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