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