]> git.saurik.com Git - apple/security.git/blob - OSX/sec/securityd/SecItemServer.c
Security-57740.60.18.tar.gz
[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 #if USE_KEYSTORE
48 #include <MobileKeyBag/MobileKeyBag.h>
49 #endif
50 // TODO: Make this include work on both platforms. rdar://problem/16526848
51 #if TARGET_OS_EMBEDDED
52 #include <Security/SecEntitlements.h>
53 #else
54 /* defines from <Security/SecEntitlements.h> */
55 #define kSecEntitlementAssociatedDomains CFSTR("com.apple.developer.associated-domains")
56 #define kSecEntitlementPrivateAssociatedDomains CFSTR("com.apple.private.associated-domains")
57 #endif
58
59 #if TARGET_OS_EMBEDDED
60 #include <AggregateDictionary/ADClient.h>
61 #include <sys/time.h>
62 #include <unistd.h>
63 #endif
64
65 #include <utilities/array_size.h>
66 #include <utilities/SecFileLocations.h>
67 #include <utilities/SecTrace.h>
68 #include <utilities/SecXPCError.h>
69 #include <Security/SecuritydXPC.h>
70 #include "swcagent_client.h"
71
72 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
73 #include <dlfcn.h>
74 #include <SharedWebCredentials/SharedWebCredentials.h>
75
76 typedef OSStatus (*SWCCheckService_f)(CFStringRef service, CFStringRef appID, CFStringRef domain, SWCCheckServiceCompletion_b completion);
77 typedef OSStatus (*SWCSetServiceFlags_f)(CFStringRef service, CFStringRef appID, CFStringRef domain, SWCFlags mask, SWCFlags flags, SWCSetServiceFlagsCompletion_b completion);
78 #else
79 typedef uint32_t SWCFlags;
80 #define kSWCFlags_None 0
81 #define kSWCFlag_Pending ( 1U << 0 )
82 #define kSWCFlag_SiteApproved ( 1U << 1 )
83 #define kSWCFlag_SiteDenied ( 1U << 2 )
84 #define kSWCFlag_UserApproved ( 1U << 3 )
85 #define kSWCFlag_UserDenied ( 1U << 4 )
86 #define kSWCFlag_ExternalMask ( kSWCFlag_UserApproved | kSWCFlag_UserDenied )
87 #endif
88
89 /* Changed the name of the keychain changed notification, for testing */
90 static const char *g_keychain_changed_notification = kSecServerKeychainChangedNotification;
91
92 void SecItemServerSetKeychainChangedNotification(const char *notification_name)
93 {
94 g_keychain_changed_notification = notification_name;
95 }
96
97 void SecKeychainChanged() {
98 uint32_t result = notify_post(g_keychain_changed_notification);
99 if (result == NOTIFY_STATUS_OK)
100 secnotice("item", "Sent %s", g_keychain_changed_notification);
101 else
102 secerror("notify_post %s returned: %" PRIu32, g_keychain_changed_notification, result);
103 }
104
105 /* 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 || (code & 0xff) == SQLITE_FULL))
607 {
608 // TODO This should not be true but SecDb code is too eager to corrupt, rdar://problem/29771874
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 #if USE_KEYSTORE
716 /*
717 * Similar to ks_open_keybag, but goes through MKB interface
718 */
719 static bool mkb_open_keybag(CFDataRef keybag, CFDataRef password, MKBKeyBagHandleRef *handle, CFErrorRef *error) {
720 kern_return_t rc;
721 MKBKeyBagHandleRef mkbhandle = NULL;
722
723 rc = MKBKeyBagCreateWithData(keybag, &mkbhandle);
724 if (rc != kMobileKeyBagSuccess) {
725 return SecKernError(rc, error, CFSTR("MKBKeyBagCreateWithData failed: %d"), rc);
726 }
727
728 if (password) {
729 rc = MKBKeyBagUnlock(mkbhandle, password);
730 if (rc != kMobileKeyBagSuccess) {
731 CFRelease(mkbhandle);
732 return SecKernError(rc, error, CFSTR("failed to unlock bag: %d"), rc);
733 }
734 }
735
736 *handle = mkbhandle;
737
738 return true;
739 }
740 #endif
741
742
743 static CFDataRef SecServerKeychainCreateBackup(SecDbConnectionRef dbt, SecurityClient *client, CFDataRef keybag,
744 CFDataRef password, CFErrorRef *error) {
745 CFDataRef backup = NULL;
746 keybag_handle_t backup_keybag;
747 #if USE_KEYSTORE
748 MKBKeyBagHandleRef mkbhandle = NULL;
749 require(mkb_open_keybag(keybag, password, &mkbhandle, error), out);
750
751 require_noerr(MKBKeyBagGetAKSHandle(mkbhandle, &backup_keybag), out);
752
753 #else
754 backup_keybag = KEYBAG_NONE;
755 #endif
756 /* Export from system keybag to backup keybag. */
757 backup = SecServerExportBackupableKeychain(dbt, client, KEYBAG_DEVICE, backup_keybag, error);
758
759 #if USE_KEYSTORE
760 out:
761 if (mkbhandle)
762 CFRelease(mkbhandle);
763 #endif
764 return backup;
765 }
766
767 static bool SecServerKeychainRestore(SecDbConnectionRef dbt,
768 SecurityClient *client,
769 CFDataRef backup,
770 CFDataRef keybag,
771 CFDataRef password,
772 CFErrorRef *error)
773 {
774 bool ok = false;
775 keybag_handle_t backup_keybag;
776 #if USE_KEYSTORE
777 MKBKeyBagHandleRef mkbhandle = NULL;
778 require(mkb_open_keybag(keybag, password, &mkbhandle, error), out);
779
780 require_noerr(MKBKeyBagGetAKSHandle(mkbhandle, &backup_keybag), out);
781 #else
782 backup_keybag = KEYBAG_NONE;
783 #endif
784 /* Import from backup keybag to system keybag. */
785 require(SecServerImportBackupableKeychain(dbt, client, backup_keybag, KEYBAG_DEVICE, backup, error), out);
786
787 ok = true;
788 out:
789 #if USE_KEYSTORE
790 if (mkbhandle)
791 CFRelease(mkbhandle);
792 #endif
793 return ok;
794 }
795
796
797 // MARK - External SPI support code.
798
799 CFStringRef __SecKeychainCopyPath(void) {
800 CFStringRef kcRelPath = NULL;
801 if (use_hwaes()) {
802 kcRelPath = CFSTR("keychain-2.db");
803 } else {
804 kcRelPath = CFSTR("keychain-2-debug.db");
805 }
806
807 CFStringRef kcPath = NULL;
808 CFURLRef kcURL = SecCopyURLForFileInKeychainDirectory(kcRelPath);
809 if (kcURL) {
810 kcPath = CFURLCopyFileSystemPath(kcURL, kCFURLPOSIXPathStyle);
811 CFRelease(kcURL);
812 }
813 return kcPath;
814 }
815
816 // MARK; -
817 // MARK: kc_dbhandle init and reset
818
819 SecDbRef SecKeychainDbCreate(CFStringRef path) {
820 return SecDbCreate(path, ^bool (SecDbConnectionRef dbconn, bool didCreate, bool *callMeAgainForNextConnection, CFErrorRef *error) {
821 // Upgrade from version 0 means create the schema in empty db.
822 int version = 0;
823 bool ok = true;
824 if (!didCreate)
825 ok = SecKeychainDbGetVersion(dbconn, &version, error);
826
827 ok = ok && SecKeychainDbUpgradeFromVersion(dbconn, version, callMeAgainForNextConnection, error);
828 if (!ok)
829 secerror("Upgrade %sfailed: %@", didCreate ? "from v0 " : "", error ? *error : NULL);
830
831 return ok;
832 });
833 }
834
835 static SecDbRef _kc_dbhandle = NULL;
836
837 static void kc_dbhandle_init(void) {
838 SecDbRef oldHandle = _kc_dbhandle;
839 _kc_dbhandle = NULL;
840 CFStringRef dbPath = __SecKeychainCopyPath();
841 if (dbPath) {
842 _kc_dbhandle = SecKeychainDbCreate(dbPath);
843 CFRelease(dbPath);
844 } else {
845 secerror("no keychain path available");
846 }
847 if (oldHandle) {
848 secerror("replaced %@ with %@", oldHandle, _kc_dbhandle);
849 CFRelease(oldHandle);
850 }
851 }
852
853 // A callback for the sqlite3_log() interface.
854 static void sqlite3Log(void *pArg, int iErrCode, const char *zMsg){
855 secdebug("sqlite3", "(%d) %s", iErrCode, zMsg);
856 }
857
858 void
859 _SecServerDatabaseSetup(void)
860 {
861 static dispatch_once_t onceToken;
862 dispatch_once(&onceToken, ^{
863 int rx = sqlite3_config(SQLITE_CONFIG_LOG, sqlite3Log, NULL);
864 if (SQLITE_OK != rx) {
865 secwarning("Could not set up sqlite global error logging to syslog: %d", rx);
866 }
867 });
868 }
869
870 static SecDbRef kc_dbhandle(void)
871 {
872 static dispatch_once_t onceToken;
873 dispatch_once(&onceToken, ^{
874 _SecServerDatabaseSetup();
875 kc_dbhandle_init();
876 });
877 return _kc_dbhandle;
878 }
879
880 /* For whitebox testing only */
881 void SecKeychainDbReset(dispatch_block_t inbetween)
882 {
883 CFStringRef dbPath = __SecKeychainCopyPath();
884 if (dbPath == NULL)
885 abort();
886
887 CFReleaseNull(_kc_dbhandle);
888
889 if (inbetween)
890 inbetween();
891
892 _kc_dbhandle = SecKeychainDbCreate(dbPath);
893 CFRelease(dbPath);
894 }
895
896 static SecDbConnectionRef kc_acquire_dbt(bool writeAndRead, CFErrorRef *error) {
897 SecDbRef db = kc_dbhandle();
898 if (db == NULL) {
899 SecError(errSecDataNotAvailable, error, CFSTR("failed to get a db handle"));
900 return NULL;
901 }
902 return SecDbConnectionAcquire(db, !writeAndRead, error);
903 }
904
905 /* Return a per thread dbt handle for the keychain. If create is true create
906 the database if it does not yet exist. If it is false, just return an
907 error if it fails to auto-create. */
908 static bool kc_with_dbt(bool writeAndRead, CFErrorRef *error, bool (^perform)(SecDbConnectionRef dbt))
909 {
910 // Make sure we initialize our engines before writing to the keychain
911 if (writeAndRead)
912 SecItemDataSourceFactoryGetDefault();
913
914 bool ok = false;
915 SecDbConnectionRef dbt = kc_acquire_dbt(writeAndRead, error);
916 if (dbt) {
917 ok = perform(dbt);
918 SecDbConnectionRelease(dbt);
919 }
920 return ok;
921 }
922
923 static bool
924 items_matching_issuer_parent(SecDbConnectionRef dbt, CFArrayRef accessGroups, CFDataRef musrView,
925 CFDataRef issuer, CFArrayRef issuers, int recurse)
926 {
927 Query *q;
928 CFArrayRef results = NULL;
929 CFIndex i, count;
930 bool found = false;
931
932 if (CFArrayContainsValue(issuers, CFRangeMake(0, CFArrayGetCount(issuers)), issuer))
933 return true;
934
935 /* XXX make musr supported */
936 const void *keys[] = { kSecClass, kSecReturnRef, kSecAttrSubject };
937 const void *vals[] = { kSecClassCertificate, kCFBooleanTrue, issuer };
938 CFDictionaryRef query = CFDictionaryCreate(kCFAllocatorDefault, keys, vals, array_size(keys), NULL, NULL);
939
940 if (!query)
941 return false;
942
943 CFErrorRef localError = NULL;
944 q = query_create_with_limit(query, musrView, kSecMatchUnlimited, &localError);
945 CFRelease(query);
946 if (q) {
947 s3dl_copy_matching(dbt, q, (CFTypeRef*)&results, accessGroups, &localError);
948 query_destroy(q, &localError);
949 }
950 if (localError) {
951 secerror("items matching issuer parent: %@", localError);
952 CFReleaseNull(localError);
953 return false;
954 }
955
956 count = CFArrayGetCount(results);
957 for (i = 0; (i < count) && !found; i++) {
958 CFDictionaryRef cert_dict = (CFDictionaryRef)CFArrayGetValueAtIndex(results, i);
959 CFDataRef cert_issuer = CFDictionaryGetValue(cert_dict, kSecAttrIssuer);
960 if (CFEqual(cert_issuer, issuer))
961 continue;
962 if (recurse-- > 0)
963 found = items_matching_issuer_parent(dbt, accessGroups, musrView, cert_issuer, issuers, recurse);
964 }
965 CFReleaseSafe(results);
966
967 return found;
968 }
969
970 static bool
971 _FilterWithPolicy(SecPolicyRef policy, CFDateRef date, SecCertificateRef cert)
972 {
973 CFDictionaryRef props = NULL;
974 CFArrayRef keychains = NULL;
975 CFArrayRef anchors = NULL;
976 CFArrayRef certs = NULL;
977 CFArrayRef chain = NULL;
978 SecTrustRef trust = NULL;
979
980 SecTrustResultType trustResult;
981 Boolean needChain = false;
982 __block bool ok = false;
983
984 if (!policy || !cert) return false;
985
986 certs = CFArrayCreate(NULL, (const void **)&cert, (CFIndex)1, &kCFTypeArrayCallBacks);
987 require_noerr_quiet(SecTrustCreateWithCertificates(certs, policy, &trust), cleanup);
988
989 /* Set evaluation date, if specified (otherwise current date is implied) */
990 if (date && (CFGetTypeID(date) == CFDateGetTypeID())) {
991 require_noerr_quiet(SecTrustSetVerifyDate(trust, date), cleanup);
992 }
993
994 /* Check whether this is the X509 Basic policy, which means chain building */
995 props = SecPolicyCopyProperties(policy);
996 if (props) {
997 CFTypeRef oid = (CFTypeRef) CFDictionaryGetValue(props, kSecPolicyOid);
998 if (oid && (CFEqual(oid, kSecPolicyAppleX509Basic) ||
999 CFEqual(oid, kSecPolicyAppleRevocation))) {
1000 needChain = true;
1001 }
1002 }
1003
1004 if (!needChain) {
1005 require_noerr_quiet(SecTrustEvaluateLeafOnly(trust, &trustResult), cleanup);
1006 } else {
1007 require_noerr_quiet(SecTrustEvaluate(trust, &trustResult), cleanup);
1008 }
1009
1010 require_quiet((trustResult == kSecTrustResultProceed ||
1011 trustResult == kSecTrustResultUnspecified ||
1012 trustResult == kSecTrustResultRecoverableTrustFailure), cleanup);
1013
1014 ok = true;
1015 #if TARGET_OS_IPHONE
1016 CFArrayRef properties = SecTrustCopyProperties(trust);
1017 #else
1018 CFArrayRef properties = SecTrustCopyProperties_ios(trust);
1019 #endif
1020 if (properties) {
1021 CFArrayForEach(properties, ^(const void *property) {
1022 CFDictionaryForEach((CFDictionaryRef)property, ^(const void *key, const void *value) {
1023 if (CFEqual((CFTypeRef)key, kSecPropertyKeyType) && CFEqual((CFTypeRef)value, kSecPropertyTypeError))
1024 ok = false;
1025 });
1026 });
1027 CFRelease(properties);
1028 }
1029
1030 cleanup:
1031 if(props) CFRelease(props);
1032 if(chain) CFRelease(chain);
1033 if(anchors) CFRelease(anchors);
1034 if(keychains) CFRelease(keychains);
1035 if(certs) CFRelease(certs);
1036 if(trust) CFRelease(trust);
1037
1038 return ok;
1039 }
1040
1041 static bool
1042 _FilterWithDate(CFDateRef validOnDate, SecCertificateRef cert)
1043 {
1044 if (!validOnDate || !cert) return false;
1045
1046 CFAbsoluteTime at, nb, na;
1047 at = CFDateGetAbsoluteTime((CFDateRef)validOnDate);
1048
1049 bool ok = true;
1050 nb = SecCertificateNotValidBefore(cert);
1051 na = SecCertificateNotValidAfter(cert);
1052
1053 if (nb == 0 || na == 0 || nb == na) {
1054 ok = false;
1055 secnotice("FilterWithDate", "certificate cannot operate");
1056 }
1057 else if (at < nb) {
1058 ok = false;
1059 secnotice("FilterWithDate", "certificate is not valid yet");
1060 }
1061 else if (at > na) {
1062 ok = false;
1063 secnotice("FilterWithDate", "certificate expired");
1064 }
1065
1066 return ok;
1067 }
1068
1069 static bool
1070 _FilterWithTrust(Boolean trustedOnly, SecCertificateRef cert)
1071 {
1072 if (!cert) return false;
1073 if (!trustedOnly) return true;
1074
1075 bool ok = false;
1076 CFArrayRef certArray = CFArrayCreate(NULL, (const void**)&cert, 1, &kCFTypeArrayCallBacks);
1077 SecTrustRef trust = NULL;
1078 SecPolicyRef policy = SecPolicyCreateBasicX509();
1079 require_quiet(policy, out);
1080
1081 require_noerr_quiet(SecTrustCreateWithCertificates(certArray, policy, &trust), out);
1082 SecTrustResultType trustResult;
1083 require_noerr_quiet(SecTrustEvaluate(trust, &trustResult), out);
1084
1085 require_quiet((trustResult == kSecTrustResultProceed ||
1086 trustResult == kSecTrustResultUnspecified), out);
1087 ok = true;
1088 out:
1089 CFReleaseSafe(trust);
1090 CFReleaseSafe(policy);
1091 CFReleaseSafe(certArray);
1092 return ok;
1093 }
1094
1095 static SecCertificateRef
1096 CopyCertificateFromItem(Query *q, CFDictionaryRef item) {
1097 SecCertificateRef certRef = NULL;
1098
1099 CFTypeRef tokenID = NULL;
1100 CFDataRef certData = NULL;
1101 if (q->q_class == &identity_class) {
1102 certData = CFDictionaryGetValue(item, kSecAttrIdentityCertificateData);
1103 tokenID = CFDictionaryGetValue(item, kSecAttrIdentityCertificateTokenID);
1104 } else if (q->q_class == &cert_class) {
1105 certData = CFDictionaryGetValue(item, kSecValueData);
1106 tokenID = CFDictionaryGetValue(item, kSecAttrTokenID);
1107 }
1108
1109 require_quiet(certData, out);
1110 if (tokenID != NULL) {
1111 CFErrorRef error = NULL;
1112 CFDataRef tokenCertData = _SecTokenItemCopyValueData(certData, &error);
1113 require_action_quiet(tokenCertData, out, { secerror("function _SecTokenItemCopyValueData failed with: %@", error); CFReleaseSafe(error); });
1114 certRef = SecCertificateCreateWithData(kCFAllocatorDefault, tokenCertData);
1115 CFRelease(tokenCertData);
1116 }
1117 else
1118 certRef = SecCertificateCreateWithData(kCFAllocatorDefault, certData);
1119
1120 out:
1121 return certRef;
1122 }
1123
1124 bool match_item(SecDbConnectionRef dbt, Query *q, CFArrayRef accessGroups, CFDictionaryRef item)
1125 {
1126 bool ok = false;
1127 SecCertificateRef certRef = NULL;
1128 if (q->q_match_issuer) {
1129 CFDataRef issuer = CFDictionaryGetValue(item, kSecAttrIssuer);
1130 if (!items_matching_issuer_parent(dbt, accessGroups, q->q_musrView, issuer, q->q_match_issuer, 10 /*max depth*/))
1131 return ok;
1132 }
1133
1134 if (q->q_match_policy && (q->q_class == &identity_class || q->q_class == &cert_class)) {
1135 if (!certRef)
1136 certRef = CopyCertificateFromItem(q, item);
1137 require_quiet(certRef, out);
1138 require_quiet(_FilterWithPolicy(q->q_match_policy, q->q_match_valid_on_date, certRef), out);
1139 }
1140
1141 if (q->q_match_valid_on_date && (q->q_class == &identity_class || q->q_class == &cert_class)) {
1142 if (!certRef)
1143 certRef = CopyCertificateFromItem(q, item);
1144 require_quiet(certRef, out);
1145 require_quiet(_FilterWithDate(q->q_match_valid_on_date, certRef), out);
1146 }
1147
1148 if (q->q_match_trusted_only && (q->q_class == &identity_class || q->q_class == &cert_class)) {
1149 if (!certRef)
1150 certRef = CopyCertificateFromItem(q, item);
1151 require_quiet(certRef, out);
1152 require_quiet(_FilterWithTrust(CFBooleanGetValue(q->q_match_trusted_only), certRef), out);
1153 }
1154
1155 /* Add future match checks here. */
1156 ok = true;
1157 out:
1158 CFReleaseSafe(certRef);
1159 return ok;
1160 }
1161
1162 /****************************************************************************
1163 **************** Beginning of Externally Callable Interface ****************
1164 ****************************************************************************/
1165
1166 void (*SecTaskDiagnoseEntitlements)(CFArrayRef accessGroups) = NULL;
1167
1168 static bool SecEntitlementError(OSStatus status, CFErrorRef *error)
1169 {
1170 #if TARGET_OS_OSX
1171 #define SEC_ENTITLEMENT_WARNING CFSTR("com.apple.application-identifier, com.apple.security.application-groups nor keychain-access-groups")
1172 #else
1173 #define SEC_ENTITLEMENT_WARNING CFSTR("application-identifier nor keychain-access-groups")
1174 #endif
1175
1176 return SecError(errSecMissingEntitlement, error, CFSTR("Client has neither %@ entitlements"), SEC_ENTITLEMENT_WARNING);
1177 }
1178
1179 /* AUDIT[securityd](done):
1180 query (ok) is a caller provided dictionary, only its cf type has been checked.
1181 */
1182 static bool
1183 SecItemServerCopyMatching(CFDictionaryRef query, CFTypeRef *result,
1184 SecurityClient *client, CFErrorRef *error)
1185 {
1186 CFArrayRef accessGroups = client->accessGroups;
1187
1188 CFIndex ag_count;
1189 if (!accessGroups || 0 == (ag_count = CFArrayGetCount(accessGroups))) {
1190 if (SecTaskDiagnoseEntitlements)
1191 SecTaskDiagnoseEntitlements(accessGroups);
1192 return SecEntitlementError(errSecMissingEntitlement, error);
1193 }
1194
1195 if (CFArrayContainsValue(accessGroups, CFRangeMake(0, ag_count), CFSTR("*"))) {
1196 /* Having the special accessGroup "*" allows access to all accessGroups. */
1197 accessGroups = NULL;
1198 }
1199
1200 bool ok = false;
1201 Query *q = query_create_with_limit(query, client->musr, 1, error);
1202 if (q) {
1203 CFStringRef agrp = CFDictionaryGetValue(q->q_item, kSecAttrAccessGroup);
1204 if (agrp && accessGroupsAllows(accessGroups, agrp)) {
1205 // TODO: Return an error if agrp is not NULL and accessGroupsAllows() fails above.
1206 const void *val = agrp;
1207 accessGroups = CFArrayCreate(0, &val, 1, &kCFTypeArrayCallBacks);
1208 } else {
1209 CFRetainSafe(accessGroups);
1210 }
1211
1212 #if TARGET_OS_IPHONE
1213 if (q->q_sync_bubble && client->inMultiUser) {
1214 CFReleaseNull(q->q_musrView);
1215 q->q_musrView = SecMUSRCreateSyncBubbleUserUUID(q->q_sync_bubble);
1216 } else if (client->inMultiUser && client->isNetworkExtension) {
1217 CFReleaseNull(q->q_musrView);
1218 q->q_musrView = SecMUSRCreateBothUserAndSystemUUID(client->uid);
1219 } else if (q->q_system_keychain && client->inMultiUser) {
1220 CFReleaseNull(q->q_musrView);
1221 q->q_musrView = SecMUSRCopySystemKeychainUUID();
1222 } else {
1223 q->q_system_keychain = false;
1224 }
1225 #endif
1226
1227 query_set_caller_access_groups(q, accessGroups);
1228
1229 /* Sanity check the query. */
1230 if (q->q_system_keychain && !client->allowSystemKeychain) {
1231 ok = SecError(errSecMissingEntitlement, error, CFSTR("client doesn't have entitlement for system keychain"));
1232 } else if (q->q_sync_bubble && !client->allowSyncBubbleKeychain) {
1233 ok = SecError(errSecMissingEntitlement, error, CFSTR("client doesn't have entitlement for syncbubble keychain"));
1234 } else if (q->q_system_keychain && q->q_sync_bubble) {
1235 ok = SecError(errSecMissingEntitlement, error, CFSTR("can't do both system and syncbubble keychain"));
1236 } else if (q->q_use_item_list) {
1237 ok = SecError(errSecUseItemListUnsupported, error, CFSTR("use item list unsupported"));
1238 } else if (q->q_match_issuer && ((q->q_class != &cert_class) &&
1239 (q->q_class != &identity_class))) {
1240 ok = SecError(errSecUnsupportedOperation, error, CFSTR("unsupported match attribute"));
1241 } else if (q->q_match_policy && ((q->q_class != &cert_class) &&
1242 (q->q_class != &identity_class))) {
1243 ok = SecError(errSecUnsupportedOperation, error, CFSTR("unsupported kSecMatchPolicy attribute"));
1244 } else if (q->q_return_type != 0 && result == NULL) {
1245 ok = SecError(errSecReturnMissingPointer, error, CFSTR("missing pointer"));
1246 } else if (!q->q_error) {
1247 ok = kc_with_dbt(false, error, ^(SecDbConnectionRef dbt) {
1248 return s3dl_copy_matching(dbt, q, result, accessGroups, error);
1249 });
1250 }
1251
1252 CFReleaseSafe(accessGroups);
1253 if (!query_destroy(q, error))
1254 ok = false;
1255 }
1256
1257 return ok;
1258 }
1259
1260 bool
1261 _SecItemCopyMatching(CFDictionaryRef query, SecurityClient *client, CFTypeRef *result, CFErrorRef *error) {
1262 return SecItemServerCopyMatching(query, result, client, error);
1263 }
1264
1265 #if TARGET_OS_IPHONE
1266 static bool
1267 SecItemSynchronizable(CFDictionaryRef query)
1268 {
1269 bool result = false;
1270 CFTypeRef value = CFDictionaryGetValue(query, kSecAttrSynchronizable);
1271 if (isBoolean(value))
1272 return CFBooleanGetValue(value);
1273 else if (isNumber(value)) {
1274 SInt32 number = 0;
1275 (void)CFNumberGetValue(value, kCFNumberSInt32Type, &number);
1276 result = !!number;
1277 }
1278
1279 return result;
1280 }
1281 #endif
1282
1283
1284 /* AUDIT[securityd](done):
1285 attributes (ok) is a caller provided dictionary, only its cf type has
1286 been checked.
1287 */
1288 bool
1289 _SecItemAdd(CFDictionaryRef attributes, SecurityClient *client, CFTypeRef *result, CFErrorRef *error)
1290 {
1291 CFArrayRef accessGroups = client->accessGroups;
1292
1293 bool ok = true;
1294 CFIndex ag_count;
1295 if (!accessGroups || 0 == (ag_count = CFArrayGetCount(accessGroups)) ||
1296 (ag_count == 1 && CFArrayContainsValue(accessGroups, CFRangeMake(0, ag_count), kSecAttrAccessGroupToken))) {
1297 if (SecTaskDiagnoseEntitlements)
1298 SecTaskDiagnoseEntitlements(accessGroups);
1299 return SecEntitlementError(errSecMissingEntitlement, error);
1300 }
1301
1302 Query *q = query_create_with_limit(attributes, client->musr, 0, error);
1303 if (q) {
1304 /* Access group sanity checking. */
1305 CFStringRef agrp = (CFStringRef)CFDictionaryGetValue(attributes,
1306 kSecAttrAccessGroup);
1307
1308 /* Having the special accessGroup "*" allows access to all accessGroups. */
1309 if (CFArrayContainsValue(accessGroups, CFRangeMake(0, ag_count), CFSTR("*")))
1310 accessGroups = NULL;
1311
1312 if (agrp) {
1313 /* The user specified an explicit access group, validate it. */
1314 if (!accessGroupsAllows(accessGroups, agrp))
1315 ok = SecError(errSecMissingEntitlement, error,
1316 CFSTR("explicit accessGroup %@ not in client access %@"), agrp, accessGroups);
1317 } else {
1318 agrp = (CFStringRef)CFArrayGetValueAtIndex(client->accessGroups, 0);
1319
1320 /* We are using an implicit access group, add it as if the user
1321 specified it as an attribute. */
1322 query_add_attribute(kSecAttrAccessGroup, agrp, q);
1323 }
1324 #if TARGET_OS_IPHONE
1325 if (q->q_system_keychain && client->inMultiUser) {
1326 CFReleaseNull(q->q_musrView);
1327 q->q_musrView = SecMUSRCopySystemKeychainUUID();
1328 } else {
1329 q->q_system_keychain = false;
1330 }
1331 query_add_attribute_with_desc(&v8musr, q->q_musrView, q);
1332 #endif
1333
1334 if (ok) {
1335 query_ensure_access_control(q, agrp);
1336
1337 if (q->q_system_keychain && !client->allowSystemKeychain) {
1338 ok = SecError(errSecMissingEntitlement, error, CFSTR("client doesn't have entitlement for system keychain"));
1339 } else if (q->q_sync_bubble && !client->allowSyncBubbleKeychain) {
1340 ok = SecError(errSecMissingEntitlement, error, CFSTR("client doesn't have entitlement for syncbubble keychain"));
1341 #if TARGET_OS_IPHONE
1342 } else if (q->q_system_keychain && SecItemSynchronizable(attributes) && !client->inMultiUser) {
1343 ok = SecError(errSecInvalidKey, error, CFSTR("Can't store system keychain and synchronizable"));
1344 #endif
1345 } else if (q->q_row_id) {
1346 ok = SecError(errSecValuePersistentRefUnsupported, error, CFSTR("q_row_id")); // TODO: better error string
1347 } else if (!q->q_error) {
1348 ok = kc_with_dbt(true, error, ^(SecDbConnectionRef dbt){
1349 return kc_transaction(dbt, error, ^{
1350 query_pre_add(q, true);
1351 return s3dl_query_add(dbt, q, result, error);
1352 });
1353 });
1354 }
1355 }
1356 ok = query_notify_and_destroy(q, ok, error);
1357 } else {
1358 ok = false;
1359 }
1360 return ok;
1361 }
1362
1363 /* AUDIT[securityd](done):
1364 query (ok) and attributesToUpdate (ok) are a caller provided dictionaries,
1365 only their cf types have been checked.
1366 */
1367 bool
1368 _SecItemUpdate(CFDictionaryRef query, CFDictionaryRef attributesToUpdate,
1369 SecurityClient *client, CFErrorRef *error)
1370 {
1371 CFArrayRef accessGroups = client->accessGroups;
1372
1373 CFIndex ag_count;
1374 if (!accessGroups || 0 == (ag_count = CFArrayGetCount(accessGroups)) ||
1375 (ag_count == 1 && CFArrayContainsValue(accessGroups, CFRangeMake(0, ag_count), kSecAttrAccessGroupToken))) {
1376 if (SecTaskDiagnoseEntitlements)
1377 SecTaskDiagnoseEntitlements(accessGroups);
1378 return SecEntitlementError(errSecMissingEntitlement, error);
1379 }
1380
1381 if (CFArrayContainsValue(accessGroups, CFRangeMake(0, ag_count), CFSTR("*"))) {
1382 /* Having the special accessGroup "*" allows access to all accessGroups. */
1383 accessGroups = NULL;
1384 }
1385
1386 bool ok = true;
1387 Query *q = query_create_with_limit(query, client->musr, kSecMatchUnlimited, error);
1388 if (!q) {
1389 ok = false;
1390 }
1391 if (ok) {
1392 #if TARGET_OS_IPHONE
1393 if (q->q_system_keychain && client->inMultiUser) {
1394 CFReleaseNull(q->q_musrView);
1395 q->q_musrView = SecMUSRCopySystemKeychainUUID();
1396 } else {
1397 q->q_system_keychain = false;
1398 }
1399 #endif
1400
1401 /* Sanity check the query. */
1402 query_set_caller_access_groups(q, accessGroups);
1403 if (q->q_system_keychain && !client->allowSystemKeychain) {
1404 ok = SecError(errSecMissingEntitlement, error, CFSTR("client doesn't have entitlement for system keychain"));
1405 } else if (q->q_sync_bubble && !client->allowSyncBubbleKeychain) {
1406 ok = SecError(errSecMissingEntitlement, error, CFSTR("client doesn't have entitlement for syncbubble keychain"));
1407 #if TARGET_OS_IPHONE
1408 } else if (q->q_system_keychain && SecItemSynchronizable(attributesToUpdate) && !client->inMultiUser) {
1409 ok = SecError(errSecInvalidKey, error, CFSTR("Can't update an system keychain item with synchronizable"));
1410 #endif
1411 } else if (q->q_use_item_list) {
1412 ok = SecError(errSecUseItemListUnsupported, error, CFSTR("use item list not supported"));
1413 } else if (q->q_return_type & kSecReturnDataMask) {
1414 /* Update doesn't return anything so don't ask for it. */
1415 ok = SecError(errSecReturnDataUnsupported, error, CFSTR("return data not supported by update"));
1416 } else if (q->q_return_type & kSecReturnAttributesMask) {
1417 ok = SecError(errSecReturnAttributesUnsupported, error, CFSTR("return attributes not supported by update"));
1418 } else if (q->q_return_type & kSecReturnRefMask) {
1419 ok = SecError(errSecReturnRefUnsupported, error, CFSTR("return ref not supported by update"));
1420 } else if (q->q_return_type & kSecReturnPersistentRefMask) {
1421 ok = SecError(errSecReturnPersistentRefUnsupported, error, CFSTR("return persistent ref not supported by update"));
1422 } else {
1423 /* Access group sanity checking. */
1424 CFStringRef agrp = (CFStringRef)CFDictionaryGetValue(attributesToUpdate,
1425 kSecAttrAccessGroup);
1426 if (agrp) {
1427 /* The user is attempting to modify the access group column,
1428 validate it to make sure the new value is allowable. */
1429 if (!accessGroupsAllows(accessGroups, agrp)) {
1430 ok = SecError(errSecNoAccessForItem, error, CFSTR("accessGroup %@ not in %@"), agrp, accessGroups);
1431 }
1432 }
1433 }
1434 }
1435 if (ok) {
1436 ok = kc_with_dbt(true, error, ^(SecDbConnectionRef dbt) {
1437 return kc_transaction(dbt, error, ^{
1438 return s3dl_query_update(dbt, q, attributesToUpdate, accessGroups, error);
1439 });
1440 });
1441 }
1442 if (q) {
1443 ok = query_notify_and_destroy(q, ok, error);
1444 }
1445 return ok;
1446 }
1447
1448
1449 /* AUDIT[securityd](done):
1450 query (ok) is a caller provided dictionary, only its cf type has been checked.
1451 */
1452 bool
1453 _SecItemDelete(CFDictionaryRef query, SecurityClient *client, CFErrorRef *error)
1454 {
1455 CFArrayRef accessGroups = client->accessGroups;
1456
1457 CFIndex ag_count;
1458 if (!accessGroups || 0 == (ag_count = CFArrayGetCount(accessGroups)) ||
1459 (ag_count == 1 && CFArrayContainsValue(accessGroups, CFRangeMake(0, ag_count), kSecAttrAccessGroupToken))) {
1460 if (SecTaskDiagnoseEntitlements)
1461 SecTaskDiagnoseEntitlements(accessGroups);
1462 return SecEntitlementError(errSecMissingEntitlement, error);
1463 }
1464
1465 if (CFArrayContainsValue(accessGroups, CFRangeMake(0, ag_count), CFSTR("*"))) {
1466 /* Having the special accessGroup "*" allows access to all accessGroups. */
1467 accessGroups = NULL;
1468 }
1469
1470 Query *q = query_create_with_limit(query, client->musr, kSecMatchUnlimited, error);
1471 bool ok;
1472 if (q) {
1473 #if TARGET_OS_IPHONE
1474 if (q->q_system_keychain && client->inMultiUser) {
1475 CFReleaseNull(q->q_musrView);
1476 q->q_musrView = SecMUSRCopySystemKeychainUUID();
1477 } else {
1478 q->q_system_keychain = false;
1479 }
1480 #endif
1481
1482 query_set_caller_access_groups(q, accessGroups);
1483 /* Sanity check the query. */
1484 if (q->q_system_keychain && !client->allowSystemKeychain) {
1485 ok = SecError(errSecMissingEntitlement, error, CFSTR("client doesn't have entitlement for system keychain"));
1486 } else if (q->q_sync_bubble && !client->allowSyncBubbleKeychain) {
1487 ok = SecError(errSecMissingEntitlement, error, CFSTR("client doesn't have entitlement for syncbubble keychain"));
1488 } else if (q->q_limit != kSecMatchUnlimited) {
1489 ok = SecError(errSecMatchLimitUnsupported, error, CFSTR("match limit not supported by delete"));
1490 } else if (query_match_count(q) != 0) {
1491 ok = SecError(errSecItemMatchUnsupported, error, CFSTR("match not supported by delete"));
1492 } else if (q->q_ref) {
1493 ok = SecError(errSecValueRefUnsupported, error, CFSTR("value ref not supported by delete"));
1494 } else if (q->q_row_id && query_attr_count(q)) {
1495 ok = SecError(errSecItemIllegalQuery, error, CFSTR("rowid and other attributes are mutually exclusive"));
1496 } else {
1497 ok = kc_with_dbt(true, error, ^(SecDbConnectionRef dbt) {
1498 return kc_transaction(dbt, error, ^{
1499 return s3dl_query_delete(dbt, q, accessGroups, error);
1500 });
1501 });
1502 }
1503 ok = query_notify_and_destroy(q, ok, error);
1504 } else {
1505 ok = false;
1506 }
1507 return ok;
1508 }
1509
1510 static bool SecItemDeleteTokenItems(SecDbConnectionRef dbt, CFTypeRef classToDelete, CFTypeRef tokenID, CFArrayRef accessGroups, SecurityClient *client, CFErrorRef *error) {
1511 CFTypeRef keys[] = { kSecClass, kSecAttrTokenID };
1512 CFTypeRef values[] = { classToDelete, tokenID };
1513
1514 CFDictionaryRef query = CFDictionaryCreate(kCFAllocatorDefault, keys, values, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1515 Query *q = query_create_with_limit(query, client->musr, kSecMatchUnlimited, error);
1516 CFRelease(query);
1517 bool ok;
1518 if (q) {
1519 query_set_caller_access_groups(q, accessGroups);
1520 ok = s3dl_query_delete(dbt, q, accessGroups, error);
1521 ok = query_notify_and_destroy(q, ok, error);
1522 } else {
1523 ok = false;
1524 }
1525
1526 return ok;
1527 }
1528
1529 static bool SecItemAddTokenItem(SecDbConnectionRef dbt, CFDictionaryRef attributes, CFArrayRef accessGroups, SecurityClient *client, CFErrorRef *error) {
1530 bool ok = true;
1531 Query *q = query_create_with_limit(attributes, client->musr, 0, error);
1532 if (q) {
1533 CFStringRef agrp = kSecAttrAccessGroupToken;
1534 query_add_attribute(kSecAttrAccessGroup, agrp, q);
1535
1536 if (ok) {
1537 query_ensure_access_control(q, agrp);
1538 if (q->q_system_keychain && !client->allowSystemKeychain) {
1539 ok = SecError(errSecMissingEntitlement, error, CFSTR("client doesn't have entitlement for system keychain"));
1540 } else if (q->q_sync_bubble && !client->allowSyncBubbleKeychain) {
1541 ok = SecError(errSecMissingEntitlement, error, CFSTR("client doesn't have entitlement for syncbubble keychain"));
1542 } else if (q->q_row_id) {
1543 ok = SecError(errSecValuePersistentRefUnsupported, error, CFSTR("q_row_id")); // TODO: better error string
1544 } else if (!q->q_error) {
1545 query_pre_add(q, true);
1546 ok = s3dl_query_add(dbt, q, NULL, error);
1547 }
1548 }
1549 ok = query_notify_and_destroy(q, ok, error);
1550 } else {
1551 return false;
1552 }
1553 return ok;
1554 }
1555
1556 bool _SecItemUpdateTokenItems(CFStringRef tokenID, CFArrayRef items, SecurityClient *client, CFErrorRef *error) {
1557 bool ok = true;
1558 CFArrayRef accessGroups = client->accessGroups;
1559 CFIndex ag_count;
1560 if (!accessGroups || 0 == (ag_count = CFArrayGetCount(accessGroups))) {
1561 if (SecTaskDiagnoseEntitlements)
1562 SecTaskDiagnoseEntitlements(accessGroups);
1563 return SecEntitlementError(errSecMissingEntitlement, error);
1564 }
1565
1566 ok = kc_with_dbt(true, error, ^bool (SecDbConnectionRef dbt) {
1567 return kc_transaction(dbt, error, ^bool {
1568 if (items) {
1569 const CFTypeRef classToDelete[] = { kSecClassGenericPassword, kSecClassInternetPassword, kSecClassCertificate, kSecClassKey };
1570 for (size_t i = 0; i < sizeof(classToDelete) / sizeof(classToDelete[0]); ++i) {
1571 SecItemDeleteTokenItems(dbt, classToDelete[i], tokenID, accessGroups, client, NULL);
1572 }
1573
1574 for (CFIndex i = 0; i < CFArrayGetCount(items); ++i) {
1575 if (!SecItemAddTokenItem(dbt, CFArrayGetValueAtIndex(items, i), accessGroups, client, error))
1576 return false;
1577 }
1578 return true;
1579 }
1580 else {
1581 const CFTypeRef classToDelete[] = { kSecClassGenericPassword, kSecClassInternetPassword, kSecClassCertificate, kSecClassKey };
1582 bool deleted = true;
1583 for (size_t i = 0; i < sizeof(classToDelete) / sizeof(classToDelete[0]); ++i) {
1584 if (!SecItemDeleteTokenItems(dbt, classToDelete[i], tokenID, accessGroups, client, error) && error && CFErrorGetCode(*error) != errSecItemNotFound) {
1585 deleted = false;
1586 break;
1587 }
1588 else if (error && *error) {
1589 CFReleaseNull(*error);
1590 }
1591 }
1592 return deleted;
1593 }
1594 });
1595 });
1596
1597 return ok;
1598 }
1599
1600 /* AUDIT[securityd](done):
1601 No caller provided inputs.
1602 */
1603 static bool
1604 SecItemServerDeleteAll(CFErrorRef *error) {
1605 return kc_with_dbt(true, error, ^bool (SecDbConnectionRef dbt) {
1606 return (kc_transaction(dbt, error, ^bool {
1607 return (SecDbExec(dbt, CFSTR("DELETE from genp;"), error) &&
1608 SecDbExec(dbt, CFSTR("DELETE from inet;"), error) &&
1609 SecDbExec(dbt, CFSTR("DELETE from cert;"), error) &&
1610 SecDbExec(dbt, CFSTR("DELETE from keys;"), error));
1611 }) && SecDbExec(dbt, CFSTR("VACUUM;"), error));
1612 });
1613 }
1614
1615 bool
1616 _SecItemDeleteAll(CFErrorRef *error) {
1617 return SecItemServerDeleteAll(error);
1618 }
1619
1620 bool
1621 _SecItemServerDeleteAllWithAccessGroups(CFArrayRef accessGroups, SecurityClient *client, CFErrorRef *error)
1622 {
1623 __block bool ok = true;
1624 static dispatch_once_t onceToken;
1625 static CFSetRef illegalAccessGroups = NULL;
1626
1627 dispatch_once(&onceToken, ^{
1628 const CFStringRef values[] = {
1629 CFSTR("*"),
1630 CFSTR("apple"),
1631 CFSTR("com.apple.security.sos"),
1632 CFSTR("lockdown-identities"),
1633 };
1634 illegalAccessGroups = CFSetCreate(NULL, (const void **)values, sizeof(values)/sizeof(values[0]), &kCFTypeSetCallBacks);
1635 });
1636
1637 static const CFTypeRef qclasses[] = {
1638 &inet_class,
1639 &genp_class,
1640 &keys_class,
1641 &cert_class
1642 };
1643
1644 require_action_quiet(isArray(accessGroups), fail,
1645 ok = false;
1646 SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType, sSecXPCErrorDomain, NULL, error, NULL, CFSTR("accessGroups not CFArray, got %@"), accessGroups));
1647
1648 // TODO: whitelist instead? look for dev IDs like 7123498YQX.com.somedev.app
1649
1650 require_action(CFArrayGetCount(accessGroups) != 0, fail,
1651 ok = false;
1652 SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType, sSecXPCErrorDomain, NULL, error, NULL, CFSTR("accessGroups e empty")));
1653
1654
1655 // Pre-check accessGroups for prohibited values
1656 CFArrayForEach(accessGroups, ^(const void *value) {
1657 CFStringRef agrp = (CFStringRef)value;
1658
1659 if (!isString(agrp)) {
1660 SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType, sSecXPCErrorDomain, NULL, error, NULL,
1661 CFSTR("access not a string: %@"), agrp);
1662 ok &= false;
1663 } else if (CFSetContainsValue(illegalAccessGroups, agrp)) {
1664 SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType, sSecXPCErrorDomain, NULL, error, NULL,
1665 CFSTR("illegal access group: %@"), accessGroups);
1666 ok &= false;
1667 }
1668 });
1669 require(ok,fail);
1670
1671 ok = kc_with_dbt(true, error, ^bool(SecDbConnectionRef dbt) {
1672 return kc_transaction(dbt, error, ^bool {
1673 CFErrorRef localError = NULL;
1674 bool ok1 = true;
1675 size_t n;
1676
1677 for (n = 0; n < sizeof(qclasses)/sizeof(qclasses[0]) && ok1; n++) {
1678 Query *q;
1679
1680 q = query_create(qclasses[n], client->musr, NULL, error);
1681 require(q, fail2);
1682
1683 (void)s3dl_query_delete(dbt, q, accessGroups, &localError);
1684 fail2:
1685 query_destroy(q, error);
1686 CFReleaseNull(localError);
1687 }
1688 return ok1;
1689 }) && SecDbExec(dbt, CFSTR("VACUUM"), error);
1690 });
1691
1692 fail:
1693 return ok;
1694 }
1695
1696
1697 // MARK: -
1698 // MARK: Shared web credentials
1699
1700 #if TARGET_OS_IOS
1701
1702 /* constants */
1703 #define SEC_CONST_DECL(k,v) const CFStringRef k = CFSTR(v);
1704
1705 SEC_CONST_DECL (kSecSafariAccessGroup, "com.apple.cfnetwork");
1706 SEC_CONST_DECL (kSecSafariDefaultComment, "default");
1707 SEC_CONST_DECL (kSecSafariPasswordsNotSaved, "Passwords not saved");
1708 SEC_CONST_DECL (kSecSharedCredentialUrlScheme, "https://");
1709 SEC_CONST_DECL (kSecSharedWebCredentialsService, "webcredentials");
1710
1711 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1712 static dispatch_once_t sSecSWCInitializeOnce = 0;
1713 static void * sSecSWCLibrary = NULL;
1714 static SWCCheckService_f sSWCCheckService_f = NULL;
1715 static SWCSetServiceFlags_f sSWCSetServiceFlags_f = NULL;
1716
1717 static OSStatus _SecSWCEnsuredInitialized(void);
1718
1719 static OSStatus _SecSWCEnsuredInitialized(void)
1720 {
1721 __block OSStatus status = errSecNotAvailable;
1722
1723 dispatch_once(&sSecSWCInitializeOnce, ^{
1724 sSecSWCLibrary = dlopen("/System/Library/PrivateFrameworks/SharedWebCredentials.framework/SharedWebCredentials", RTLD_LAZY | RTLD_LOCAL);
1725 assert(sSecSWCLibrary);
1726 if (sSecSWCLibrary) {
1727 sSWCCheckService_f = (SWCCheckService_f)(uintptr_t) dlsym(sSecSWCLibrary, "SWCCheckService");
1728 sSWCSetServiceFlags_f = (SWCSetServiceFlags_f)(uintptr_t) dlsym(sSecSWCLibrary, "SWCSetServiceFlags");
1729 }
1730 });
1731
1732 if (sSWCCheckService_f && sSWCSetServiceFlags_f) {
1733 status = noErr;
1734 }
1735 return status;
1736 }
1737 #endif
1738
1739 #if !TARGET_IPHONE_SIMULATOR
1740 static SWCFlags
1741 _SecAppDomainApprovalStatus(CFStringRef appID, CFStringRef fqdn, CFErrorRef *error)
1742 {
1743 __block SWCFlags flags = kSWCFlags_None;
1744
1745 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1746 OSStatus status = _SecSWCEnsuredInitialized();
1747 if (status) {
1748 SecError(status, error, CFSTR("SWC initialize failed"));
1749 return flags;
1750 }
1751 CFRetainSafe(appID);
1752 CFRetainSafe(fqdn);
1753 dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
1754 dispatch_retain(semaphore);
1755 if (0 == sSWCCheckService_f(kSecSharedWebCredentialsService, appID, fqdn,
1756 ^void (OSStatus inStatus, SWCFlags inFlags, CFDictionaryRef inDetails) {
1757 if (!inStatus) { flags = inFlags; }
1758 CFReleaseSafe(appID);
1759 CFReleaseSafe(fqdn);
1760 dispatch_semaphore_signal(semaphore);
1761 dispatch_release(semaphore);
1762 //secerror("SWCCheckService: inStatus=%d, flags=%0X", inStatus, flags);
1763 }))
1764 {
1765 // wait for the block to complete, as we need its answer
1766 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
1767 }
1768 else // didn't queue the block
1769 {
1770 CFReleaseSafe(appID);
1771 CFReleaseSafe(fqdn);
1772 dispatch_release(semaphore);
1773 }
1774 dispatch_release(semaphore);
1775 #else
1776 flags |= (kSWCFlag_SiteApproved);
1777 #endif
1778
1779 if (!error) { return flags; }
1780 *error = NULL;
1781
1782 // check website approval status
1783 if (!(flags & kSWCFlag_SiteApproved)) {
1784 if (flags & kSWCFlag_Pending) {
1785 SecError(errSecAuthFailed, error, CFSTR("Approval is pending for \"%@\", try later"), fqdn);
1786 } else {
1787 SecError(errSecAuthFailed, error, CFSTR("\"%@\" failed to approve \"%@\""), fqdn, appID);
1788 }
1789 return flags;
1790 }
1791
1792 // check user approval status
1793 if (flags & kSWCFlag_UserDenied) {
1794 SecError(errSecAuthFailed, error, CFSTR("User denied access to \"%@\" by \"%@\""), fqdn, appID);
1795 }
1796 return flags;
1797 }
1798 #endif
1799
1800 #if !TARGET_IPHONE_SIMULATOR
1801 static bool
1802 _SecEntitlementContainsDomainForService(CFArrayRef domains, CFStringRef domain, CFStringRef service)
1803 {
1804 bool result = false;
1805 CFIndex idx, count = (domains) ? CFArrayGetCount(domains) : (CFIndex) 0;
1806 if (!count || !domain || !service) {
1807 return result;
1808 }
1809 for (idx=0; idx < count; idx++) {
1810 CFStringRef str = (CFStringRef) CFArrayGetValueAtIndex(domains, idx);
1811 if (str && CFStringHasPrefix(str, kSecSharedWebCredentialsService)) {
1812 CFIndex prefix_len = CFStringGetLength(kSecSharedWebCredentialsService)+1;
1813 CFIndex substr_len = CFStringGetLength(str) - prefix_len;
1814 CFRange range = { prefix_len, substr_len };
1815 CFStringRef substr = CFStringCreateWithSubstring(kCFAllocatorDefault, str, range);
1816 if (substr && CFEqual(substr, domain)) {
1817 result = true;
1818 }
1819 CFReleaseSafe(substr);
1820 if (result) {
1821 break;
1822 }
1823 }
1824 }
1825 return result;
1826 }
1827 #endif
1828
1829 static bool
1830 _SecAddNegativeWebCredential(SecurityClient *client, CFStringRef fqdn, CFStringRef appID, bool forSafari)
1831 {
1832 bool result = false;
1833 if (!fqdn) { return result; }
1834
1835 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1836 OSStatus status = _SecSWCEnsuredInitialized();
1837 if (status) { return false; }
1838
1839 // update our database
1840 CFRetainSafe(appID);
1841 CFRetainSafe(fqdn);
1842 if (0 == sSWCSetServiceFlags_f(kSecSharedWebCredentialsService,
1843 appID, fqdn, kSWCFlag_ExternalMask, kSWCFlag_UserDenied,
1844 ^void(OSStatus inStatus, SWCFlags inNewFlags){
1845 CFReleaseSafe(appID);
1846 CFReleaseSafe(fqdn);
1847 }))
1848 {
1849 result = true;
1850 }
1851 else // didn't queue the block
1852 {
1853 CFReleaseSafe(appID);
1854 CFReleaseSafe(fqdn);
1855 }
1856 #endif
1857 if (!forSafari) { return result; }
1858
1859 // below this point: create a negative Safari web credential item
1860
1861 CFMutableDictionaryRef attrs = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1862 if (!attrs) { return result; }
1863
1864 CFErrorRef error = NULL;
1865 CFStringRef accessGroup = CFSTR("*");
1866 SecurityClient swcclient = {
1867 .task = NULL,
1868 .accessGroups = CFArrayCreate(kCFAllocatorDefault, (const void **)&accessGroup, 1, &kCFTypeArrayCallBacks),
1869 .allowSystemKeychain = false,
1870 .allowSyncBubbleKeychain = false,
1871 .isNetworkExtension = false,
1872 .musr = client->musr,
1873 };
1874
1875 CFDictionaryAddValue(attrs, kSecClass, kSecClassInternetPassword);
1876 CFDictionaryAddValue(attrs, kSecAttrAccessGroup, kSecSafariAccessGroup);
1877 CFDictionaryAddValue(attrs, kSecAttrAuthenticationType, kSecAttrAuthenticationTypeHTMLForm);
1878 CFDictionaryAddValue(attrs, kSecAttrProtocol, kSecAttrProtocolHTTPS);
1879 CFDictionaryAddValue(attrs, kSecAttrServer, fqdn);
1880 CFDictionaryAddValue(attrs, kSecAttrSynchronizable, kCFBooleanTrue);
1881
1882 (void)_SecItemDelete(attrs, &swcclient, &error);
1883 CFReleaseNull(error);
1884
1885 CFDictionaryAddValue(attrs, kSecAttrAccount, kSecSafariPasswordsNotSaved);
1886 CFDictionaryAddValue(attrs, kSecAttrComment, kSecSafariDefaultComment);
1887
1888 CFStringRef label = CFStringCreateWithFormat(kCFAllocatorDefault,
1889 NULL, CFSTR("%@ (%@)"), fqdn, kSecSafariPasswordsNotSaved);
1890 if (label) {
1891 CFDictionaryAddValue(attrs, kSecAttrLabel, label);
1892 CFReleaseSafe(label);
1893 }
1894
1895 UInt8 space = ' ';
1896 CFDataRef data = CFDataCreate(kCFAllocatorDefault, &space, 1);
1897 if (data) {
1898 CFDictionarySetValue(attrs, kSecValueData, data);
1899 CFReleaseSafe(data);
1900 }
1901
1902 CFTypeRef addResult = NULL;
1903 result = _SecItemAdd(attrs, &swcclient, &addResult, &error);
1904
1905 CFReleaseSafe(addResult);
1906 CFReleaseSafe(error);
1907 CFReleaseSafe(attrs);
1908 CFReleaseSafe(swcclient.accessGroups);
1909
1910 return result;
1911 }
1912
1913 /* Specialized version of SecItemAdd for shared web credentials */
1914 bool
1915 _SecAddSharedWebCredential(CFDictionaryRef attributes,
1916 SecurityClient *client,
1917 const audit_token_t *clientAuditToken,
1918 CFStringRef appID,
1919 CFArrayRef domains,
1920 CFTypeRef *result,
1921 CFErrorRef *error)
1922 {
1923
1924 SecurityClient swcclient = {};
1925
1926 CFStringRef fqdn = CFRetainSafe(CFDictionaryGetValue(attributes, kSecAttrServer));
1927 CFStringRef account = CFDictionaryGetValue(attributes, kSecAttrAccount);
1928 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH && !TARGET_OS_TV
1929 CFStringRef password = CFDictionaryGetValue(attributes, kSecSharedPassword);
1930 #else
1931 CFStringRef password = CFDictionaryGetValue(attributes, CFSTR("spwd"));
1932 #endif
1933 CFStringRef accessGroup = CFSTR("*");
1934 CFMutableDictionaryRef query = NULL, attrs = NULL;
1935 SInt32 port = -1;
1936 bool ok = false;
1937
1938 // check autofill enabled status
1939 if (!swca_autofill_enabled(clientAuditToken)) {
1940 SecError(errSecBadReq, error, CFSTR("Autofill is not enabled in Safari settings"));
1941 goto cleanup;
1942 }
1943
1944 // parse fqdn with CFURL here, since it could be specified as domain:port
1945 if (fqdn) {
1946 CFStringRef urlStr = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@%@"), kSecSharedCredentialUrlScheme, fqdn);
1947 if (urlStr) {
1948 CFURLRef url = CFURLCreateWithString(kCFAllocatorDefault, urlStr, nil);
1949 if (url) {
1950 CFStringRef hostname = CFURLCopyHostName(url);
1951 if (hostname) {
1952 CFReleaseSafe(fqdn);
1953 fqdn = hostname;
1954 port = CFURLGetPortNumber(url);
1955 }
1956 CFReleaseSafe(url);
1957 }
1958 CFReleaseSafe(urlStr);
1959 }
1960 }
1961
1962 if (!account) {
1963 SecError(errSecParam, error, CFSTR("No account provided"));
1964 goto cleanup;
1965 }
1966 if (!fqdn) {
1967 SecError(errSecParam, error, CFSTR("No domain provided"));
1968 goto cleanup;
1969 }
1970
1971 #if TARGET_IPHONE_SIMULATOR
1972 secerror("app/site association entitlements not checked in Simulator");
1973 #else
1974 OSStatus status = errSecMissingEntitlement;
1975 // validate that fqdn is part of caller's shared credential domains entitlement
1976 if (!appID) {
1977 SecError(status, error, CFSTR("Missing application-identifier entitlement"));
1978 goto cleanup;
1979 }
1980 if (_SecEntitlementContainsDomainForService(domains, fqdn, kSecSharedWebCredentialsService)) {
1981 status = errSecSuccess;
1982 }
1983 if (errSecSuccess != status) {
1984 CFStringRef msg = CFStringCreateWithFormat(kCFAllocatorDefault, NULL,
1985 CFSTR("%@ not found in %@ entitlement"), fqdn, kSecEntitlementAssociatedDomains);
1986 if (!msg) {
1987 msg = CFRetain(CFSTR("Requested domain not found in entitlement"));
1988 }
1989 SecError(status, error, CFSTR("%@"), msg);
1990 CFReleaseSafe(msg);
1991 goto cleanup;
1992 }
1993 #endif
1994
1995 #if TARGET_IPHONE_SIMULATOR
1996 secerror("Ignoring app/site approval state in the Simulator.");
1997 #else
1998 // get approval status for this app/domain pair
1999 SWCFlags flags = _SecAppDomainApprovalStatus(appID, fqdn, error);
2000 if (!(flags & kSWCFlag_SiteApproved)) {
2001 goto cleanup;
2002 }
2003 #endif
2004
2005 // give ourselves access to see matching items for kSecSafariAccessGroup
2006 swcclient.task = NULL;
2007 swcclient.accessGroups = CFArrayCreate(kCFAllocatorDefault, (const void **)&accessGroup, 1, &kCFTypeArrayCallBacks);
2008 swcclient.allowSystemKeychain = false;
2009 swcclient.musr = client->musr;
2010 swcclient.allowSystemKeychain = false;
2011 swcclient.allowSyncBubbleKeychain = false;
2012 swcclient.isNetworkExtension = false;
2013
2014
2015 // create lookup query
2016 query = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2017 if (!query) {
2018 SecError(errSecAllocate, error, CFSTR("Unable to create query dictionary"));
2019 goto cleanup;
2020 }
2021 CFDictionaryAddValue(query, kSecClass, kSecClassInternetPassword);
2022 CFDictionaryAddValue(query, kSecAttrAccessGroup, kSecSafariAccessGroup);
2023 CFDictionaryAddValue(query, kSecAttrAuthenticationType, kSecAttrAuthenticationTypeHTMLForm);
2024 CFDictionaryAddValue(query, kSecAttrServer, fqdn);
2025 CFDictionaryAddValue(query, kSecAttrSynchronizable, kCFBooleanTrue);
2026
2027 // check for presence of Safari's negative entry ('passwords not saved')
2028 CFDictionarySetValue(query, kSecAttrAccount, kSecSafariPasswordsNotSaved);
2029 ok = _SecItemCopyMatching(query, &swcclient, result, error);
2030 if(result) CFReleaseNull(*result);
2031 if (error) CFReleaseNull(*error);
2032 if (ok) {
2033 SecError(errSecDuplicateItem, error, CFSTR("Item already exists for this server"));
2034 goto cleanup;
2035 }
2036
2037 // now use the provided account (and optional port number, if one was present)
2038 CFDictionarySetValue(query, kSecAttrAccount, account);
2039 if (port < -1 || port > 0) {
2040 SInt16 portValueShort = (port & 0xFFFF);
2041 CFNumberRef portNumber = CFNumberCreate(NULL, kCFNumberSInt16Type, &portValueShort);
2042 CFDictionaryAddValue(query, kSecAttrPort, portNumber);
2043 CFReleaseSafe(portNumber);
2044 }
2045
2046 // look up existing password
2047 if (_SecItemCopyMatching(query, &swcclient, result, error)) {
2048 // found it, so this becomes either an "update password" or "delete password" operation
2049 if(result) CFReleaseNull(*result);
2050 if(error) CFReleaseNull(*error);
2051 bool update = (password != NULL);
2052 if (update) {
2053 attrs = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2054 CFDataRef credential = CFStringCreateExternalRepresentation(kCFAllocatorDefault, password, kCFStringEncodingUTF8, 0);
2055 CFDictionaryAddValue(attrs, kSecValueData, credential);
2056 CFReleaseSafe(credential);
2057 CFDictionaryAddValue(attrs, kSecAttrComment, kSecSafariDefaultComment);
2058
2059 // confirm the update
2060 // (per rdar://16676310 we always prompt, even if there was prior user approval)
2061 ok = /*approved ||*/ swca_confirm_operation(swca_update_request_id, clientAuditToken, query, error,
2062 ^void (CFStringRef fqdn) { _SecAddNegativeWebCredential(client, fqdn, appID, false); });
2063 if (ok) {
2064 ok = _SecItemUpdate(query, attrs, &swcclient, error);
2065 }
2066 }
2067 else {
2068 // confirm the delete
2069 // (per rdar://16676288 we always prompt, even if there was prior user approval)
2070 ok = /*approved ||*/ swca_confirm_operation(swca_delete_request_id, clientAuditToken, query, error,
2071 ^void (CFStringRef fqdn) { _SecAddNegativeWebCredential(client, fqdn, appID, false); });
2072 if (ok) {
2073 ok = _SecItemDelete(query, &swcclient, error);
2074 }
2075 }
2076 if (ok) {
2077 if (error) CFReleaseNull(*error);
2078 }
2079 goto cleanup;
2080 }
2081 if (result) CFReleaseNull(*result);
2082 if (error) CFReleaseNull(*error);
2083
2084 // password does not exist, so prepare to add it
2085 if (!password) {
2086 // a NULL password value removes the existing credential. Since we didn't find it, this is a no-op.
2087 ok = true;
2088 goto cleanup;
2089 }
2090 else {
2091 CFStringRef label = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@ (%@)"), fqdn, account);
2092 if (label) {
2093 CFDictionaryAddValue(query, kSecAttrLabel, label);
2094 CFReleaseSafe(label);
2095 }
2096 // NOTE: we always expect to use HTTPS for web forms.
2097 CFDictionaryAddValue(query, kSecAttrProtocol, kSecAttrProtocolHTTPS);
2098
2099 CFDataRef credential = CFStringCreateExternalRepresentation(kCFAllocatorDefault, password, kCFStringEncodingUTF8, 0);
2100 CFDictionarySetValue(query, kSecValueData, credential);
2101 CFReleaseSafe(credential);
2102 CFDictionarySetValue(query, kSecAttrComment, kSecSafariDefaultComment);
2103
2104 CFReleaseSafe(swcclient.accessGroups);
2105 swcclient.accessGroups = CFArrayCreate(kCFAllocatorDefault, (const void **)&kSecSafariAccessGroup, 1, &kCFTypeArrayCallBacks);
2106
2107 // mark the item as created by this function
2108 const int32_t creator_value = 'swca';
2109 CFNumberRef creator = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &creator_value);
2110 if (creator) {
2111 CFDictionarySetValue(query, kSecAttrCreator, creator);
2112 CFReleaseSafe(creator);
2113 ok = true;
2114 }
2115 else {
2116 // confirm the add
2117 // (per rdar://16680019, we won't prompt here in the normal case)
2118 ok = /*approved ||*/ swca_confirm_operation(swca_add_request_id, clientAuditToken, query, error,
2119 ^void (CFStringRef fqdn) { _SecAddNegativeWebCredential(client, fqdn, appID, false); });
2120 }
2121 }
2122 if (ok) {
2123 ok = _SecItemAdd(query, &swcclient, result, error);
2124 }
2125
2126 cleanup:
2127 CFReleaseSafe(attrs);
2128 CFReleaseSafe(query);
2129 CFReleaseSafe(swcclient.accessGroups);
2130 CFReleaseSafe(fqdn);
2131 return ok;
2132 }
2133
2134 /* Specialized version of SecItemCopyMatching for shared web credentials */
2135 bool
2136 _SecCopySharedWebCredential(CFDictionaryRef query,
2137 SecurityClient *client,
2138 const audit_token_t *clientAuditToken,
2139 CFStringRef appID,
2140 CFArrayRef domains,
2141 CFTypeRef *result,
2142 CFErrorRef *error)
2143 {
2144 CFMutableArrayRef credentials = NULL;
2145 CFMutableArrayRef foundItems = NULL;
2146 CFMutableArrayRef fqdns = NULL;
2147 CFStringRef fqdn = NULL;
2148 CFStringRef account = NULL;
2149 CFIndex idx, count;
2150 SInt32 port = -1;
2151 bool ok = false;
2152
2153 require_quiet(result, cleanup);
2154 credentials = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
2155 foundItems = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
2156 fqdns = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
2157
2158 // give ourselves access to see matching items for kSecSafariAccessGroup
2159 CFStringRef accessGroup = CFSTR("*");
2160 SecurityClient swcclient = {
2161 .task = NULL,
2162 .accessGroups = CFArrayCreate(kCFAllocatorDefault, (const void **)&accessGroup, 1, &kCFTypeArrayCallBacks),
2163 .allowSystemKeychain = false,
2164 .allowSyncBubbleKeychain = false,
2165 .isNetworkExtension = false,
2166 .musr = client->musr,
2167 };
2168
2169 // On input, the query dictionary contains optional fqdn and account entries.
2170 fqdn = CFDictionaryGetValue(query, kSecAttrServer);
2171 account = CFDictionaryGetValue(query, kSecAttrAccount);
2172
2173 // Check autofill enabled status
2174 if (!swca_autofill_enabled(clientAuditToken)) {
2175 SecError(errSecBadReq, error, CFSTR("Autofill is not enabled in Safari settings"));
2176 goto cleanup;
2177 }
2178
2179 // Check fqdn; if NULL, add domains from caller's entitlement.
2180 if (fqdn) {
2181 CFArrayAppendValue(fqdns, fqdn);
2182 }
2183 else if (domains) {
2184 CFIndex idx, count = CFArrayGetCount(domains);
2185 for (idx=0; idx < count; idx++) {
2186 CFStringRef str = (CFStringRef) CFArrayGetValueAtIndex(domains, idx);
2187 // Parse the entry for our service label prefix
2188 if (str && CFStringHasPrefix(str, kSecSharedWebCredentialsService)) {
2189 CFIndex prefix_len = CFStringGetLength(kSecSharedWebCredentialsService)+1;
2190 CFIndex substr_len = CFStringGetLength(str) - prefix_len;
2191 CFRange range = { prefix_len, substr_len };
2192 fqdn = CFStringCreateWithSubstring(kCFAllocatorDefault, str, range);
2193 if (fqdn) {
2194 CFArrayAppendValue(fqdns, fqdn);
2195 CFRelease(fqdn);
2196 }
2197 }
2198 }
2199 }
2200 count = CFArrayGetCount(fqdns);
2201 if (count < 1) {
2202 SecError(errSecParam, error, CFSTR("No domain provided"));
2203 goto cleanup;
2204 }
2205
2206 // Aggregate search results for each domain
2207 for (idx = 0; idx < count; idx++) {
2208 CFMutableArrayRef items = NULL;
2209 CFMutableDictionaryRef attrs = NULL;
2210 fqdn = (CFStringRef) CFArrayGetValueAtIndex(fqdns, idx);
2211 CFRetainSafe(fqdn);
2212 port = -1;
2213
2214 // Parse the fqdn for a possible port specifier.
2215 if (fqdn) {
2216 CFStringRef urlStr = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@%@"), kSecSharedCredentialUrlScheme, fqdn);
2217 if (urlStr) {
2218 CFURLRef url = CFURLCreateWithString(kCFAllocatorDefault, urlStr, nil);
2219 if (url) {
2220 CFStringRef hostname = CFURLCopyHostName(url);
2221 if (hostname) {
2222 CFReleaseSafe(fqdn);
2223 fqdn = hostname;
2224 port = CFURLGetPortNumber(url);
2225 }
2226 CFReleaseSafe(url);
2227 }
2228 CFReleaseSafe(urlStr);
2229 }
2230 }
2231
2232 #if TARGET_IPHONE_SIMULATOR
2233 secerror("app/site association entitlements not checked in Simulator");
2234 #else
2235 OSStatus status = errSecMissingEntitlement;
2236 if (!appID) {
2237 SecError(status, error, CFSTR("Missing application-identifier entitlement"));
2238 CFReleaseSafe(fqdn);
2239 goto cleanup;
2240 }
2241 // validate that fqdn is part of caller's entitlement
2242 if (_SecEntitlementContainsDomainForService(domains, fqdn, kSecSharedWebCredentialsService)) {
2243 status = errSecSuccess;
2244 }
2245 if (errSecSuccess != status) {
2246 CFStringRef msg = CFStringCreateWithFormat(kCFAllocatorDefault, NULL,
2247 CFSTR("%@ not found in %@ entitlement"), fqdn, kSecEntitlementAssociatedDomains);
2248 if (!msg) {
2249 msg = CFRetain(CFSTR("Requested domain not found in entitlement"));
2250 }
2251 SecError(status, error, CFSTR("%@"), msg);
2252 CFReleaseSafe(msg);
2253 CFReleaseSafe(fqdn);
2254 goto cleanup;
2255 }
2256 #endif
2257
2258 attrs = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2259 if (!attrs) {
2260 SecError(errSecAllocate, error, CFSTR("Unable to create query dictionary"));
2261 CFReleaseSafe(fqdn);
2262 goto cleanup;
2263 }
2264 CFDictionaryAddValue(attrs, kSecClass, kSecClassInternetPassword);
2265 CFDictionaryAddValue(attrs, kSecAttrAccessGroup, kSecSafariAccessGroup);
2266 CFDictionaryAddValue(attrs, kSecAttrProtocol, kSecAttrProtocolHTTPS);
2267 CFDictionaryAddValue(attrs, kSecAttrAuthenticationType, kSecAttrAuthenticationTypeHTMLForm);
2268 CFDictionaryAddValue(attrs, kSecAttrServer, fqdn);
2269 if (account) {
2270 CFDictionaryAddValue(attrs, kSecAttrAccount, account);
2271 }
2272 if (port < -1 || port > 0) {
2273 SInt16 portValueShort = (port & 0xFFFF);
2274 CFNumberRef portNumber = CFNumberCreate(NULL, kCFNumberSInt16Type, &portValueShort);
2275 CFDictionaryAddValue(attrs, kSecAttrPort, portNumber);
2276 CFReleaseSafe(portNumber);
2277 }
2278 CFDictionaryAddValue(attrs, kSecAttrSynchronizable, kCFBooleanTrue);
2279 CFDictionaryAddValue(attrs, kSecMatchLimit, kSecMatchLimitAll);
2280 CFDictionaryAddValue(attrs, kSecReturnAttributes, kCFBooleanTrue);
2281 CFDictionaryAddValue(attrs, kSecReturnData, kCFBooleanTrue);
2282
2283 ok = _SecItemCopyMatching(attrs, &swcclient, (CFTypeRef*)&items, error);
2284 if (count > 1) {
2285 // ignore interim error since we have multiple domains to search
2286 CFReleaseNull(*error);
2287 }
2288 if (ok && items && CFGetTypeID(items) == CFArrayGetTypeID()) {
2289 #if TARGET_IPHONE_SIMULATOR
2290 secerror("Ignoring app/site approval state in the Simulator.");
2291 bool approved = true;
2292 #else
2293 // get approval status for this app/domain pair
2294 SWCFlags flags = _SecAppDomainApprovalStatus(appID, fqdn, error);
2295 if (count > 1) {
2296 // ignore interim error since we have multiple domains to check
2297 CFReleaseNull(*error);
2298 }
2299 bool approved = (flags & kSWCFlag_SiteApproved);
2300 #endif
2301 if (approved) {
2302 CFArrayAppendArray(foundItems, items, CFRangeMake(0, CFArrayGetCount(items)));
2303 }
2304 }
2305 CFReleaseSafe(items);
2306 CFReleaseSafe(attrs);
2307 CFReleaseSafe(fqdn);
2308 }
2309
2310 // If matching credentials are found, the credentials provided to the completionHandler
2311 // will be a CFArrayRef containing CFDictionaryRef entries. Each dictionary entry will
2312 // contain the following pairs (see Security/SecItem.h):
2313 // key: kSecAttrServer value: CFStringRef (the website)
2314 // key: kSecAttrAccount value: CFStringRef (the account)
2315 // key: kSecSharedPassword value: CFStringRef (the password)
2316 // Optional keys:
2317 // key: kSecAttrPort value: CFNumberRef (the port number, if non-standard for https)
2318
2319 count = CFArrayGetCount(foundItems);
2320 for (idx = 0; idx < count; idx++) {
2321 CFDictionaryRef dict = (CFDictionaryRef) CFArrayGetValueAtIndex(foundItems, idx);
2322 CFMutableDictionaryRef newdict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2323 if (newdict && dict && CFGetTypeID(dict) == CFDictionaryGetTypeID()) {
2324 CFStringRef srvr = CFDictionaryGetValue(dict, kSecAttrServer);
2325 CFStringRef acct = CFDictionaryGetValue(dict, kSecAttrAccount);
2326 CFNumberRef pnum = CFDictionaryGetValue(dict, kSecAttrPort);
2327 CFStringRef icmt = CFDictionaryGetValue(dict, kSecAttrComment);
2328 CFDataRef data = CFDictionaryGetValue(dict, kSecValueData);
2329 if (srvr) {
2330 CFDictionaryAddValue(newdict, kSecAttrServer, srvr);
2331 }
2332 if (acct) {
2333 CFDictionaryAddValue(newdict, kSecAttrAccount, acct);
2334 }
2335 if (pnum) {
2336 SInt16 pval = -1;
2337 if (CFNumberGetValue(pnum, kCFNumberSInt16Type, &pval) &&
2338 (pval < -1 || pval > 0)) {
2339 CFDictionaryAddValue(newdict, kSecAttrPort, pnum);
2340 }
2341 }
2342 if (data) {
2343 CFStringRef password = CFStringCreateFromExternalRepresentation(kCFAllocatorDefault, data, kCFStringEncodingUTF8);
2344 if (password) {
2345 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH && !TARGET_OS_TV
2346 CFDictionaryAddValue(newdict, kSecSharedPassword, password);
2347 #else
2348 CFDictionaryAddValue(newdict, CFSTR("spwd"), password);
2349 #endif
2350 CFReleaseSafe(password);
2351 }
2352 }
2353 if (icmt && CFEqual(icmt, kSecSafariDefaultComment)) {
2354 CFArrayInsertValueAtIndex(credentials, 0, newdict);
2355 } else {
2356 CFArrayAppendValue(credentials, newdict);
2357 }
2358 }
2359 CFReleaseSafe(newdict);
2360 }
2361
2362 if (count) {
2363
2364 ok = false;
2365
2366 // create a new array of dictionaries (without the actual password) for picker UI
2367 count = CFArrayGetCount(credentials);
2368 CFMutableArrayRef items = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
2369 for (idx = 0; idx < count; idx++) {
2370 CFDictionaryRef dict = (CFDictionaryRef) CFArrayGetValueAtIndex(credentials, idx);
2371 CFMutableDictionaryRef newdict = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, dict);
2372 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH && !TARGET_OS_TV
2373 CFDictionaryRemoveValue(newdict, kSecSharedPassword);
2374 #else
2375 CFDictionaryRemoveValue(newdict, CFSTR("spwd"));
2376 #endif
2377 CFArrayAppendValue(items, newdict);
2378 CFReleaseSafe(newdict);
2379 }
2380
2381 // prompt user to select one of the dictionary items
2382 CFDictionaryRef selected = swca_copy_selected_dictionary(swca_select_request_id,
2383 clientAuditToken, items, error);
2384 if (selected) {
2385 // find the matching item in our credentials array
2386 CFStringRef srvr = CFDictionaryGetValue(selected, kSecAttrServer);
2387 CFStringRef acct = CFDictionaryGetValue(selected, kSecAttrAccount);
2388 CFNumberRef pnum = CFDictionaryGetValue(selected, kSecAttrPort);
2389 for (idx = 0; idx < count; idx++) {
2390 CFDictionaryRef dict = (CFDictionaryRef) CFArrayGetValueAtIndex(credentials, idx);
2391 CFStringRef srvr1 = CFDictionaryGetValue(dict, kSecAttrServer);
2392 CFStringRef acct1 = CFDictionaryGetValue(dict, kSecAttrAccount);
2393 CFNumberRef pnum1 = CFDictionaryGetValue(dict, kSecAttrPort);
2394
2395 if (!srvr || !srvr1 || !CFEqual(srvr, srvr1)) continue;
2396 if (!acct || !acct1 || !CFEqual(acct, acct1)) continue;
2397 if ((pnum && pnum1) && !CFEqual(pnum, pnum1)) continue;
2398
2399 // we have a match!
2400 CFReleaseSafe(selected);
2401 CFRetainSafe(dict);
2402 selected = dict;
2403 ok = true;
2404 break;
2405 }
2406 }
2407 CFReleaseSafe(items);
2408 CFArrayRemoveAllValues(credentials);
2409 if (selected && ok) {
2410 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
2411 fqdn = CFDictionaryGetValue(selected, kSecAttrServer);
2412 #endif
2413 CFArrayAppendValue(credentials, selected);
2414 }
2415
2416 if (ok) {
2417 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
2418 // register confirmation with database
2419 OSStatus status = _SecSWCEnsuredInitialized();
2420 if (status) {
2421 SecError(status, error, CFSTR("SWC initialize failed"));
2422 ok = false;
2423 CFReleaseSafe(selected);
2424 goto cleanup;
2425 }
2426 CFRetainSafe(appID);
2427 CFRetainSafe(fqdn);
2428 if (0 != sSWCSetServiceFlags_f(kSecSharedWebCredentialsService,
2429 appID, fqdn, kSWCFlag_ExternalMask, kSWCFlag_UserApproved,
2430 ^void(OSStatus inStatus, SWCFlags inNewFlags){
2431 CFReleaseSafe(appID);
2432 CFReleaseSafe(fqdn);
2433 }))
2434 {
2435 // we didn't queue the block
2436 CFReleaseSafe(appID);
2437 CFReleaseSafe(fqdn);
2438 }
2439 #endif
2440 }
2441 CFReleaseSafe(selected);
2442 }
2443 else if (NULL == *error) {
2444 // found no items, and we haven't already filled in the error
2445 SecError(errSecItemNotFound, error, CFSTR("no matching items found"));
2446 }
2447
2448 cleanup:
2449 if (!ok) {
2450 CFArrayRemoveAllValues(credentials);
2451 CFReleaseNull(credentials);
2452 }
2453 CFReleaseSafe(foundItems);
2454 *result = credentials;
2455 CFReleaseSafe(swcclient.accessGroups);
2456 CFReleaseSafe(fqdns);
2457
2458 return ok;
2459 }
2460
2461 #endif /* TARGET_OS_IOS */
2462
2463
2464 // MARK: -
2465 // MARK: Keychain backup
2466
2467 CF_RETURNS_RETAINED CFDataRef
2468 _SecServerKeychainCreateBackup(SecurityClient *client, CFDataRef keybag, CFDataRef passcode, CFErrorRef *error) {
2469 CFDataRef backup;
2470 SecDbConnectionRef dbt = SecDbConnectionAcquire(kc_dbhandle(), false, error);
2471
2472 if (!dbt)
2473 return NULL;
2474
2475 if (keybag == NULL && passcode == NULL) {
2476 #if USE_KEYSTORE
2477 backup = SecServerExportBackupableKeychain(dbt, client, KEYBAG_DEVICE, backup_keybag_handle, error);
2478 #else /* !USE_KEYSTORE */
2479 (void)client;
2480 SecError(errSecParam, error, CFSTR("Why are you doing this?"));
2481 backup = NULL;
2482 #endif /* USE_KEYSTORE */
2483 } else {
2484 backup = SecServerKeychainCreateBackup(dbt, client, keybag, passcode, error);
2485 }
2486
2487 SecDbConnectionRelease(dbt);
2488
2489 return backup;
2490 }
2491
2492 bool
2493 _SecServerKeychainRestore(CFDataRef backup, SecurityClient *client, CFDataRef keybag, CFDataRef passcode, CFErrorRef *error) {
2494 if (backup == NULL || keybag == NULL)
2495 return SecError(errSecParam, error, CFSTR("backup or keybag missing"));
2496
2497 __block bool ok = true;
2498 ok &= SecDbPerformWrite(kc_dbhandle(), error, ^(SecDbConnectionRef dbconn) {
2499 ok = SecServerKeychainRestore(dbconn, client, backup, keybag, passcode, error);
2500 });
2501
2502 if (ok) {
2503 SecKeychainChanged();
2504 }
2505
2506 return ok;
2507 }
2508
2509 CFStringRef
2510 _SecServerBackupCopyUUID(CFDataRef data, CFErrorRef *error)
2511 {
2512 CFStringRef uuid = NULL;
2513 CFDictionaryRef backup;
2514
2515 backup = CFPropertyListCreateWithData(kCFAllocatorDefault, data,
2516 kCFPropertyListImmutable, NULL,
2517 error);
2518 if (isDictionary(backup)) {
2519 uuid = SecServerBackupGetKeybagUUID(backup);
2520 if (uuid)
2521 CFRetain(uuid);
2522 }
2523 CFReleaseNull(backup);
2524
2525 return uuid;
2526 }
2527
2528
2529
2530 // MARK: -
2531 // MARK: SecItemDataSource
2532
2533 // Make sure to call this before any writes to the keychain, so that we fire
2534 // up the engines to monitor manifest changes.
2535 SOSDataSourceFactoryRef SecItemDataSourceFactoryGetDefault(void) {
2536 return SecItemDataSourceFactoryGetShared(kc_dbhandle());
2537 }
2538
2539 /* AUDIT[securityd]:
2540 args_in (ok) is a caller provided, CFDictionaryRef.
2541 */
2542
2543 CF_RETURNS_RETAINED CFArrayRef
2544 _SecServerKeychainSyncUpdateMessage(CFDictionaryRef updates, CFErrorRef *error) {
2545 // This never fails, trust us!
2546 return SOSCCHandleUpdateMessage(updates);
2547 }
2548
2549 //
2550 // Truthiness in the cloud backup/restore support.
2551 //
2552
2553 static CFDictionaryRef
2554 _SecServerCopyTruthInTheCloud(CFDataRef keybag, CFDataRef password,
2555 CFDictionaryRef backup, CFErrorRef *error)
2556 {
2557 SOSManifestRef mold = NULL, mnow = NULL, mdelete = NULL, madd = NULL;
2558 __block CFMutableDictionaryRef backup_new = NULL;
2559 keybag_handle_t bag_handle;
2560 if (!ks_open_keybag(keybag, password, &bag_handle, error))
2561 return backup_new;
2562
2563 // We need to have a datasource singleton for protection domain
2564 // kSecAttrAccessibleWhenUnlocked and keep a single shared engine
2565 // instance around which we create in the datasource constructor as well.
2566 SOSDataSourceFactoryRef dsf = SecItemDataSourceFactoryGetDefault();
2567 SOSDataSourceRef ds = SOSDataSourceFactoryCreateDataSource(dsf, kSecAttrAccessibleWhenUnlocked, error);
2568 if (ds) {
2569 backup_new = backup ? CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, backup) : CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2570 mold = SOSCreateManifestWithBackup(backup, error);
2571 SOSEngineRef engine = SOSDataSourceGetSharedEngine(ds, error);
2572 mnow = SOSEngineCopyManifest(engine, NULL);
2573 if (!mnow) {
2574 mnow = SOSDataSourceCopyManifestWithViewNameSet(ds, SOSViewsGetV0ViewSet(), error);
2575 }
2576 if (!mnow) {
2577 CFReleaseNull(backup_new);
2578 secerror("failed to obtain manifest for keychain: %@", error ? *error : NULL);
2579 } else {
2580 SOSManifestDiff(mold, mnow, &mdelete, &madd, error);
2581 }
2582
2583 // Delete everything from the new_backup that is no longer in the datasource according to the datasources manifest.
2584 SOSManifestForEach(mdelete, ^(CFDataRef digest_data, bool *stop) {
2585 CFStringRef deleted_item_key = CFDataCopyHexString(digest_data);
2586 CFDictionaryRemoveValue(backup_new, deleted_item_key);
2587 CFRelease(deleted_item_key);
2588 });
2589
2590 CFMutableArrayRef changes = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
2591 SOSDataSourceForEachObject(ds, NULL, madd, error, ^void(CFDataRef digest, SOSObjectRef object, bool *stop) {
2592 CFErrorRef localError = NULL;
2593 CFDataRef digest_data = NULL;
2594 CFTypeRef value = NULL;
2595 if (!object) {
2596 // Key in our manifest can't be found in db, remove it from our manifest
2597 SOSChangesAppendDelete(changes, digest);
2598 } else if (!(digest_data = SOSObjectCopyDigest(ds, object, &localError))
2599 || !(value = SOSObjectCopyBackup(ds, object, bag_handle, &localError))) {
2600 if (SecErrorGetOSStatus(localError) == errSecDecode) {
2601 // Ignore decode errors, pretend the objects aren't there
2602 CFRelease(localError);
2603 // Object undecodable, remove it from our manifest
2604 SOSChangesAppendDelete(changes, digest);
2605 } else {
2606 // Stop iterating and propagate out all other errors.
2607 *stop = true;
2608 *error = localError;
2609 CFReleaseNull(backup_new);
2610 }
2611 } else {
2612 // TODO: Should we skip tombstones here?
2613 CFStringRef key = CFDataCopyHexString(digest_data);
2614 CFDictionarySetValue(backup_new, key, value);
2615 CFReleaseSafe(key);
2616 }
2617 CFReleaseSafe(digest_data);
2618 CFReleaseSafe(value);
2619 }) || CFReleaseNull(backup_new);
2620
2621 if (CFArrayGetCount(changes)) {
2622 if (!SOSEngineUpdateChanges(engine, kSOSDataSourceSOSTransaction, changes, error)) {
2623 CFReleaseNull(backup_new);
2624 }
2625 }
2626 CFReleaseSafe(changes);
2627
2628 SOSDataSourceRelease(ds, error) || CFReleaseNull(backup_new);
2629 }
2630
2631 CFReleaseSafe(mold);
2632 CFReleaseSafe(mnow);
2633 CFReleaseSafe(madd);
2634 CFReleaseSafe(mdelete);
2635 ks_close_keybag(bag_handle, error) || CFReleaseNull(backup_new);
2636
2637 return backup_new;
2638 }
2639
2640 static bool
2641 _SecServerRestoreTruthInTheCloud(CFDataRef keybag, CFDataRef password, CFDictionaryRef backup_in, CFErrorRef *error) {
2642 __block bool ok = true;
2643 keybag_handle_t bag_handle;
2644 if (!ks_open_keybag(keybag, password, &bag_handle, error))
2645 return false;
2646
2647 SOSManifestRef mbackup = SOSCreateManifestWithBackup(backup_in, error);
2648 if (mbackup) {
2649 SOSDataSourceFactoryRef dsf = SecItemDataSourceFactoryGetDefault();
2650 SOSDataSourceRef ds = SOSDataSourceFactoryCreateDataSource(dsf, kSecAttrAccessibleWhenUnlocked, error);
2651 ok &= ds && SOSDataSourceWith(ds, error, ^(SOSTransactionRef txn, bool *commit) {
2652 SOSManifestRef mnow = SOSDataSourceCopyManifestWithViewNameSet(ds, SOSViewsGetV0BackupViewSet(), error);
2653 SOSManifestRef mdelete = NULL, madd = NULL;
2654 SOSManifestDiff(mnow, mbackup, &mdelete, &madd, error);
2655
2656 // Don't delete everything in datasource not in backup.
2657
2658 // Add items from the backup
2659 SOSManifestForEach(madd, ^void(CFDataRef e, bool *stop) {
2660 CFDictionaryRef item = NULL;
2661 CFStringRef sha1 = CFDataCopyHexString(e);
2662 if (sha1) {
2663 item = CFDictionaryGetValue(backup_in, sha1);
2664 CFRelease(sha1);
2665 }
2666 if (item) {
2667 CFErrorRef localError = NULL;
2668
2669 if (!SOSObjectRestoreObject(ds, txn, bag_handle, item, &localError)) {
2670 OSStatus status = SecErrorGetOSStatus(localError);
2671 if (status == errSecDuplicateItem) {
2672 // Log and ignore duplicate item errors during restore
2673 secnotice("titc", "restore %@ not replacing existing item", item);
2674 } else if (status == errSecDecode) {
2675 // Log and ignore corrupted item errors during restore
2676 secnotice("titc", "restore %@ skipping corrupted item %@", item, localError);
2677 } else {
2678 if (status == errSecInteractionNotAllowed)
2679 *stop = true;
2680 // Propagate the first other error upwards (causing the restore to fail).
2681 secerror("restore %@ failed %@", item, localError);
2682 ok = false;
2683 if (error && !*error) {
2684 *error = localError;
2685 localError = NULL;
2686 }
2687 }
2688 CFReleaseSafe(localError);
2689 }
2690 }
2691 });
2692 ok &= SOSDataSourceRelease(ds, error);
2693 CFReleaseNull(mdelete);
2694 CFReleaseNull(madd);
2695 CFReleaseNull(mnow);
2696 });
2697 CFRelease(mbackup);
2698 }
2699
2700 ok &= ks_close_keybag(bag_handle, error);
2701
2702 return ok;
2703 }
2704
2705
2706 CF_RETURNS_RETAINED CFDictionaryRef
2707 _SecServerBackupSyncable(CFDictionaryRef backup, CFDataRef keybag, CFDataRef password, CFErrorRef *error) {
2708 require_action_quiet(isData(keybag), errOut, SecError(errSecParam, error, CFSTR("keybag %@ not a data"), keybag));
2709 require_action_quiet(!backup || isDictionary(backup), errOut, SecError(errSecParam, error, CFSTR("backup %@ not a dictionary"), backup));
2710 require_action_quiet(!password || isData(password), errOut, SecError(errSecParam, error, CFSTR("password %@ not a data"), password));
2711
2712 return _SecServerCopyTruthInTheCloud(keybag, password, backup, error);
2713
2714 errOut:
2715 return NULL;
2716 }
2717
2718 bool
2719 _SecServerRestoreSyncable(CFDictionaryRef backup, CFDataRef keybag, CFDataRef password, CFErrorRef *error) {
2720 bool ok;
2721 require_action_quiet(isData(keybag), errOut, ok = SecError(errSecParam, error, CFSTR("keybag %@ not a data"), keybag));
2722 require_action_quiet(isDictionary(backup), errOut, ok = SecError(errSecParam, error, CFSTR("backup %@ not a dictionary"), backup));
2723 if (password) {
2724
2725 require_action_quiet(isData(password), errOut, ok = SecError(errSecParam, error, CFSTR("password not a data")));
2726 }
2727
2728 ok = _SecServerRestoreTruthInTheCloud(keybag, password, backup, error);
2729
2730 errOut:
2731 return ok;
2732 }
2733
2734 bool _SecServerRollKeysGlue(bool force, CFErrorRef *error) {
2735 return _SecServerRollKeys(force, NULL, error);
2736 }
2737
2738
2739 bool _SecServerRollKeys(bool force, SecurityClient *client, CFErrorRef *error) {
2740 #if USE_KEYSTORE
2741 uint32_t keystore_generation_status = 0;
2742 if (aks_generation(KEYBAG_DEVICE, generation_noop, &keystore_generation_status))
2743 return false;
2744 uint32_t current_generation = keystore_generation_status & generation_current;
2745
2746 return kc_with_dbt(true, error, ^(SecDbConnectionRef dbt) {
2747 bool up_to_date = s3dl_dbt_keys_current(dbt, current_generation, NULL);
2748
2749 if (force && !up_to_date) {
2750 up_to_date = s3dl_dbt_update_keys(dbt, client, error);
2751 if (up_to_date) {
2752 secerror("Completed roll keys.");
2753 up_to_date = s3dl_dbt_keys_current(dbt, current_generation, NULL);
2754 }
2755 if (!up_to_date)
2756 secerror("Failed to roll keys.");
2757 }
2758 return up_to_date;
2759 });
2760 #else
2761 return true;
2762 #endif
2763 }
2764
2765 #if TARGET_OS_IOS
2766
2767 /*
2768 * Sync bubble migration code
2769 */
2770
2771 struct SyncBubbleRule {
2772 CFStringRef attribute;
2773 CFTypeRef value;
2774 };
2775
2776 static bool
2777 TransmogrifyItemsToSyncBubble(SecurityClient *client, uid_t uid,
2778 bool onlyDelete,
2779 bool copyToo,
2780 const SecDbClass *qclass,
2781 struct SyncBubbleRule *items, CFIndex nItems,
2782 CFErrorRef *error)
2783 {
2784 CFMutableDictionaryRef updateAttributes = NULL;
2785 CFDataRef syncBubbleView = NULL;
2786 CFDataRef activeUserView = NULL;
2787 bool res = false;
2788 Query *q = NULL;
2789 CFIndex n;
2790
2791 syncBubbleView = SecMUSRCreateSyncBubbleUserUUID(uid);
2792 require(syncBubbleView, fail);
2793
2794 activeUserView = SecMUSRCreateActiveUserUUID(uid);
2795 require(activeUserView, fail);
2796
2797
2798 if ((onlyDelete && !copyToo) || !onlyDelete) {
2799
2800 /*
2801 * Clean out items first
2802 */
2803
2804 secnotice("syncbubble", "cleaning out old items");
2805
2806 q = query_create(qclass, NULL, NULL, error);
2807 require(q, fail);
2808
2809 q->q_limit = kSecMatchUnlimited;
2810 q->q_keybag = device_keybag_handle;
2811
2812 for (n = 0; n < nItems; n++) {
2813 query_add_attribute(items[n].attribute, items[n].value, q);
2814 }
2815 q->q_musrView = CFRetain(syncBubbleView);
2816 require(q->q_musrView, fail);
2817
2818 kc_with_dbt(false, error, ^(SecDbConnectionRef dbt) {
2819 return kc_transaction(dbt, error, ^{
2820 return s3dl_query_delete(dbt, q, NULL, error);
2821 });
2822 });
2823
2824 query_destroy(q, NULL);
2825 q = NULL;
2826 }
2827
2828
2829 if (onlyDelete || !copyToo) {
2830 secnotice("syncbubble", "skip migration of items");
2831 } else {
2832 /*
2833 * Copy over items from EMCS to sync bubble
2834 */
2835
2836 secnotice("syncbubble", "migrating sync bubble items");
2837
2838 q = query_create(qclass, NULL, NULL, error);
2839 require(q, fail);
2840
2841 q->q_return_type = kSecReturnDataMask | kSecReturnAttributesMask;
2842 q->q_limit = kSecMatchUnlimited;
2843 q->q_keybag = device_keybag_handle; /* XXX change to session key bag when it exists */
2844
2845 for (n = 0; n < nItems; n++) {
2846 query_add_or_attribute(items[n].attribute, items[n].value, q);
2847 }
2848 query_add_or_attribute(CFSTR("musr"), activeUserView, q);
2849 q->q_musrView = CFRetain(activeUserView);
2850
2851 updateAttributes = CFDictionaryCreateMutableForCFTypes(NULL);
2852 require(updateAttributes, fail);
2853
2854 CFDictionarySetValue(updateAttributes, CFSTR("musr"), syncBubbleView); /* XXX should use kSecAttrMultiUser */
2855
2856
2857 kc_with_dbt(true, error, ^(SecDbConnectionRef dbt) {
2858 return kc_transaction(dbt, error, ^{
2859 CFErrorRef error2 = NULL;
2860
2861 SecDbItemSelect(q, dbt, &error2, NULL, ^bool(const SecDbAttr *attr) {
2862 return CFDictionaryGetValue(q->q_item, attr->name);
2863 }, NULL, NULL, ^(SecDbItemRef item, bool *stop) {
2864 CFErrorRef error3 = NULL;
2865 secinfo("syncbubble", "migrating item");
2866
2867 SecDbItemRef new_item = SecDbItemCopyWithUpdates(item, updateAttributes, NULL);
2868 if (new_item == NULL)
2869 return;
2870
2871 SecDbItemClearRowId(new_item, NULL);
2872
2873 if (!SecDbItemSetKeybag(new_item, device_keybag_handle, NULL)) {
2874 CFRelease(new_item);
2875 return;
2876 }
2877
2878 if (!SecDbItemInsert(new_item, dbt, &error3)) {
2879 secnotice("syncbubble", "migration failed with %@ for item %@", error3, new_item);
2880 }
2881 CFRelease(new_item);
2882 CFReleaseNull(error3);
2883 });
2884 CFReleaseNull(error2);
2885
2886 return (bool)true;
2887 });
2888 });
2889 }
2890 res = true;
2891
2892 fail:
2893 CFReleaseNull(syncBubbleView);
2894 CFReleaseNull(activeUserView);
2895 CFReleaseNull(updateAttributes);
2896 if (q)
2897 query_destroy(q, NULL);
2898
2899 return res;
2900 }
2901
2902 static struct SyncBubbleRule PCSItems[] = {
2903 {
2904 .attribute = CFSTR("agrp"),
2905 .value = CFSTR("com.apple.ProtectedCloudStorage"),
2906 }
2907 };
2908 static struct SyncBubbleRule NSURLSesssiond[] = {
2909 {
2910 .attribute = CFSTR("agrp"),
2911 .value = CFSTR("com.apple.nsurlsessiond"),
2912 }
2913 };
2914 static struct SyncBubbleRule AccountsdItems[] = {
2915 {
2916 .attribute = CFSTR("svce"),
2917 .value = CFSTR("com.apple.account.AppleAccount.token"),
2918 },
2919 {
2920 .attribute = CFSTR("svce"),
2921 .value = CFSTR("com.apple.account.AppleAccount.password"),
2922 },
2923 {
2924 .attribute = CFSTR("svce"),
2925 .value = CFSTR("com.apple.account.AppleAccount.rpassword"),
2926 },
2927 {
2928 .attribute = CFSTR("svce"),
2929 .value = CFSTR("com.apple.account.idms.token"),
2930 },
2931 {
2932 .attribute = CFSTR("svce"),
2933 .value = CFSTR("com.apple.account.idms.continuation-key"),
2934 },
2935 {
2936 .attribute = CFSTR("svce"),
2937 .value = CFSTR("com.apple.account.CloudKit.token"),
2938 },
2939 };
2940
2941 static struct SyncBubbleRule MobileMailItems[] = {
2942 {
2943 .attribute = CFSTR("svce"),
2944 .value = CFSTR("com.apple.account.IMAP.password"),
2945 },
2946 {
2947 .attribute = CFSTR("svce"),
2948 .value = CFSTR("com.apple.account.SMTP.password"),
2949 },
2950 {
2951 .attribute = CFSTR("svce"),
2952 .value = CFSTR("com.apple.account.Exchange.password"),
2953 },
2954 {
2955 .attribute = CFSTR("svce"),
2956 .value = CFSTR("com.apple.account.Hotmail.password"),
2957 },
2958 {
2959 .attribute = CFSTR("svce"),
2960 .value = CFSTR("com.apple.account.Google.password"),
2961 },
2962 {
2963 .attribute = CFSTR("svce"),
2964 .value = CFSTR("com.apple.account.Google.oauth-token"),
2965 },
2966 {
2967 .attribute = CFSTR("svce"),
2968 .value = CFSTR("com.apple.account.Google.oath-refresh-token"),
2969 },
2970 {
2971 .attribute = CFSTR("svce"),
2972 .value = CFSTR("com.apple.account.Yahoo.password"),
2973 },
2974 {
2975 .attribute = CFSTR("svce"),
2976 .value = CFSTR("com.apple.account.Yahoo.oauth-token"),
2977 },
2978 {
2979 .attribute = CFSTR("svce"),
2980 .value = CFSTR("com.apple.account.Yahoo.oauth-token-nosync"),
2981 },
2982 {
2983 .attribute = CFSTR("svce"),
2984 .value = CFSTR("com.apple.account.Yahoo.oath-refresh-token"),
2985 },
2986 {
2987 .attribute = CFSTR("svce"),
2988 .value = CFSTR("com.apple.account.IMAPNotes.password"),
2989 },
2990 {
2991 .attribute = CFSTR("svce"),
2992 .value = CFSTR("com.apple.account.IMAPMail.password"),
2993 },
2994 {
2995 .attribute = CFSTR("svce"),
2996 .value = CFSTR("com.apple.account.126.password"),
2997 },
2998 {
2999 .attribute = CFSTR("svce"),
3000 .value = CFSTR("com.apple.account.163.password"),
3001 },
3002 {
3003 .attribute = CFSTR("svce"),
3004 .value = CFSTR("com.apple.account.aol.password"),
3005 },
3006 };
3007
3008 static bool
3009 ArrayContains(CFArrayRef array, CFStringRef service)
3010 {
3011 return CFArrayContainsValue(array, CFRangeMake(0, CFArrayGetCount(array)), service);
3012 }
3013
3014 bool
3015 _SecServerTransmogrifyToSyncBubble(CFArrayRef services, uid_t uid, SecurityClient *client, CFErrorRef *error)
3016 {
3017 bool copyCloudAuthToken = false;
3018 bool copyMobileMail = false;
3019 bool res = true;
3020 bool copyPCS = false;
3021 bool onlyDelete = false;
3022 bool copyNSURLSesssion = false;
3023
3024 if (!client->inMultiUser)
3025 return false;
3026
3027 secnotice("syncbubble", "migration for uid %d uid for services %@", (int)uid, services);
3028
3029 #if TARGET_OS_SIMULATOR
3030 // no delete in sim
3031 #elif TARGET_OS_IOS
3032 if (uid != (uid_t)client->activeUser)
3033 onlyDelete = true;
3034 #else
3035 #error "no sync bubble on other platforms"
3036 #endif
3037
3038 /*
3039 * First select that services to copy/delete
3040 */
3041
3042 if (ArrayContains(services, CFSTR("com.apple.bird.usermanager.sync"))
3043 || ArrayContains(services, CFSTR("com.apple.cloudphotod.sync"))
3044 || ArrayContains(services, CFSTR("com.apple.cloudphotod.syncstakeholder"))
3045 || ArrayContains(services, CFSTR("com.apple.cloudd.usermanager.sync")))
3046 {
3047 copyCloudAuthToken = true;
3048 copyPCS = true;
3049 }
3050
3051 if (ArrayContains(services, CFSTR("com.apple.nsurlsessiond.usermanager.sync")))
3052 {
3053 copyCloudAuthToken = true;
3054 copyNSURLSesssion = true;
3055 }
3056
3057 if (ArrayContains(services, CFSTR("com.apple.syncdefaultsd.usermanager.sync"))) {
3058 copyCloudAuthToken = true;
3059 }
3060 if (ArrayContains(services, CFSTR("com.apple.mailq.sync")) || ArrayContains(services, CFSTR("com.apple.mailq.sync.xpc"))) {
3061 copyCloudAuthToken = true;
3062 copyMobileMail = true;
3063 copyPCS = true;
3064 }
3065
3066 /*
3067 * The actually copy/delete the items selected
3068 */
3069
3070 res = TransmogrifyItemsToSyncBubble(client, uid, onlyDelete, copyPCS, &inet_class, PCSItems, sizeof(PCSItems)/sizeof(PCSItems[0]), error);
3071 require(res, fail);
3072 res = TransmogrifyItemsToSyncBubble(client, uid, onlyDelete, copyPCS, &genp_class, PCSItems, sizeof(PCSItems)/sizeof(PCSItems[0]), error);
3073 require(res, fail);
3074
3075 /* mail */
3076 res = TransmogrifyItemsToSyncBubble(client, uid, onlyDelete, copyMobileMail, &genp_class, MobileMailItems, sizeof(MobileMailItems)/sizeof(MobileMailItems[0]), error);
3077 require(res, fail);
3078
3079 /* accountsd */
3080 res = TransmogrifyItemsToSyncBubble(client, uid, onlyDelete, copyCloudAuthToken, &genp_class, AccountsdItems, sizeof(AccountsdItems)/sizeof(AccountsdItems[0]), error);
3081 require(res, fail);
3082
3083 /* nsurlsessiond */
3084 res = TransmogrifyItemsToSyncBubble(client, uid, onlyDelete, copyNSURLSesssion, &inet_class, NSURLSesssiond, sizeof(NSURLSesssiond)/sizeof(NSURLSesssiond[0]), error);
3085 require(res, fail);
3086
3087 fail:
3088 return res;
3089 }
3090
3091 /*
3092 * Migrate from user keychain to system keychain when switching to edu mode
3093 */
3094
3095 bool
3096 _SecServerTransmogrifyToSystemKeychain(SecurityClient *client, CFErrorRef *error)
3097 {
3098 __block bool ok = true;
3099
3100 /*
3101 * we are not in multi user yet, about to switch, otherwise we would
3102 * check that for client->inMultiuser here
3103 */
3104
3105 kc_with_dbt(true, error, ^(SecDbConnectionRef dbt) {
3106 return kc_transaction(dbt, error, ^{
3107 CFDataRef systemUUID = SecMUSRGetSystemKeychainUUID();
3108
3109 const SecDbSchema *newSchema = kc_schemas[0];
3110 SecDbClass const *const *kcClass;
3111
3112 for (kcClass = newSchema->classes; *kcClass != NULL; kcClass++) {
3113 CFErrorRef localError = NULL;
3114 Query *q = NULL;
3115
3116 if (*kcClass == &tversion_class || *kcClass == &identity_class)
3117 continue;
3118
3119 q = query_create(*kcClass, SecMUSRGetSingleUserKeychainUUID(), NULL, error);
3120 if (q == NULL)
3121 continue;
3122
3123 ok &= SecDbItemSelect(q, dbt, error, ^bool(const SecDbAttr *attr) {
3124 return (attr->flags & kSecDbInFlag) != 0;
3125 }, ^bool(const SecDbAttr *attr) {
3126 // No filtering please.
3127 return false;
3128 }, ^bool(CFMutableStringRef sql, bool *needWhere) {
3129 SecDbAppendWhereOrAnd(sql, needWhere);
3130 CFStringAppendFormat(sql, NULL, CFSTR("musr = ?"));
3131 return true;
3132 }, ^bool(sqlite3_stmt *stmt, int col) {
3133 return SecDbBindObject(stmt, col++, SecMUSRGetSingleUserKeychainUUID(), error);
3134 }, ^(SecDbItemRef item, bool *stop) {
3135 CFErrorRef localError = NULL;
3136
3137 if (!SecDbItemSetValueWithName(item, kSecAttrMultiUser, systemUUID, &localError)) {
3138 secerror("item: %@ update musr to system failed: %@", item, localError);
3139 ok = false;
3140 goto out;
3141 }
3142
3143 if (!SecDbItemDoUpdate(item, item, dbt, &localError, ^bool (const SecDbAttr *attr) {
3144 return attr->kind == kSecDbRowIdAttr;
3145 })) {
3146 secerror("item: %@ insert during UPDATE: %@", item, localError);
3147 ok = false;
3148 goto out;
3149 }
3150
3151 out:
3152 SecErrorPropagate(localError, error);
3153 CFReleaseSafe(localError);
3154 });
3155
3156 if (q)
3157 query_destroy(q, &localError);
3158
3159 }
3160 return (bool)true;
3161 });
3162 });
3163
3164 return ok;
3165 }
3166
3167 /*
3168 * Delete account from local usage
3169 */
3170
3171 bool
3172 _SecServerDeleteMUSERViews(SecurityClient *client, uid_t uid, CFErrorRef *error)
3173 {
3174 return kc_with_dbt(true, error, ^(SecDbConnectionRef dbt) {
3175 CFDataRef musrView = NULL, syncBubbleView = NULL;
3176 bool ok = false;
3177
3178 syncBubbleView = SecMUSRCreateSyncBubbleUserUUID(uid);
3179 require(syncBubbleView, fail);
3180
3181 musrView = SecMUSRCreateActiveUserUUID(uid);
3182 require(musrView, fail);
3183
3184 require(ok = SecServerDeleteAllForUser(dbt, syncBubbleView, false, error), fail);
3185 require(ok = SecServerDeleteAllForUser(dbt, musrView, false, error), fail);
3186
3187 fail:
3188 CFReleaseNull(syncBubbleView);
3189 CFReleaseNull(musrView);
3190 return ok;
3191 });
3192 }
3193
3194
3195 #endif /* TARGET_OS_IOS */
3196
3197 bool
3198 _SecServerGetKeyStats(const SecDbClass *qclass,
3199 struct _SecServerKeyStats *stats)
3200 {
3201 __block CFErrorRef error = NULL;
3202 bool res = false;
3203
3204 Query *q = query_create(qclass, NULL, NULL, &error);
3205 require(q, fail);
3206
3207 q->q_return_type = kSecReturnDataMask | kSecReturnAttributesMask;
3208 q->q_limit = kSecMatchUnlimited;
3209 q->q_keybag = KEYBAG_DEVICE;
3210 query_add_or_attribute(kSecAttrAccessible, kSecAttrAccessibleWhenUnlocked, q);
3211 query_add_or_attribute(kSecAttrAccessible, kSecAttrAccessibleAfterFirstUnlock, q);
3212 query_add_or_attribute(kSecAttrAccessible, kSecAttrAccessibleAlways, q);
3213 query_add_or_attribute(kSecAttrAccessible, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, q);
3214 query_add_or_attribute(kSecAttrAccessible, kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, q);
3215 query_add_or_attribute(kSecAttrAccessible, kSecAttrAccessibleAlwaysThisDeviceOnly, q);
3216 query_add_attribute(kSecAttrTombstone, kCFBooleanFalse, q);
3217
3218 kc_with_dbt(false, &error, ^(SecDbConnectionRef dbconn) {
3219 CFErrorRef error2 = NULL;
3220 __block CFIndex totalSize = 0;
3221 stats->maxDataSize = 0;
3222
3223 SecDbItemSelect(q, dbconn, &error2, NULL, ^bool(const SecDbAttr *attr) {
3224 return CFDictionaryContainsKey(q->q_item, attr->name);
3225 }, NULL, NULL, ^(SecDbItemRef item, bool *stop) {
3226 CFErrorRef error3 = NULL;
3227 CFDataRef data = SecDbItemGetValue(item, &v6v_Data, &error3);
3228 if (isData(data)) {
3229 CFIndex size = CFDataGetLength(data);
3230 if (size > stats->maxDataSize)
3231 stats->maxDataSize = size;
3232 totalSize += size;
3233 stats->items++;
3234 }
3235 CFReleaseNull(error3);
3236 });
3237 CFReleaseNull(error2);
3238 if (stats->items)
3239 stats->averageSize = totalSize / stats->items;
3240
3241 return (bool)true;
3242 });
3243
3244
3245 res = true;
3246
3247 fail:
3248 CFReleaseNull(error);
3249 if (q)
3250 query_destroy(q, NULL);
3251 return res;
3252 }