]> git.saurik.com Git - apple/security.git/blob - OSX/sec/securityd/SecItemServer.c
Security-57337.60.2.tar.gz
[apple/security.git] / OSX / sec / securityd / SecItemServer.c
1 /*
2 * Copyright (c) 2006-2015 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24 /*
25 * SecItemServer.c - CoreFoundation-based constants and functions for
26 access to Security items (certificates, keys, identities, and
27 passwords.)
28 */
29
30 #include <securityd/SecItemServer.h>
31
32 #include <notify.h>
33 #include <securityd/SecItemDataSource.h>
34 #include <securityd/SecItemDb.h>
35 #include <securityd/SecItemSchema.h>
36 #include <securityd/SOSCloudCircleServer.h>
37 #include <Security/SecBasePriv.h>
38 #include <Security/SecItemPriv.h>
39 #include <Security/SecItemInternal.h>
40 #include <Security/SecureObjectSync/SOSChangeTracker.h>
41 #include <Security/SecureObjectSync/SOSDigestVector.h>
42 #include <Security/SecureObjectSync/SOSViews.h>
43
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_WATCH
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 kc_transaction(dbt, error, ^{
1123 return s3dl_query_update(dbt, q, attributesToUpdate, accessGroups, error);
1124 });
1125 });
1126 }
1127 if (q) {
1128 ok = query_notify_and_destroy(q, ok, error);
1129 }
1130 return ok;
1131 }
1132
1133
1134 /* AUDIT[securityd](done):
1135 query (ok) is a caller provided dictionary, only its cf type has been checked.
1136 */
1137 bool
1138 _SecItemDelete(CFDictionaryRef query, SecurityClient *client, CFErrorRef *error)
1139 {
1140 CFArrayRef accessGroups = client->accessGroups;
1141
1142 CFIndex ag_count;
1143 if (!accessGroups || 0 == (ag_count = CFArrayGetCount(accessGroups))) {
1144 if (SecTaskDiagnoseEntitlements)
1145 SecTaskDiagnoseEntitlements(accessGroups);
1146 return SecError(errSecMissingEntitlement, error,
1147 CFSTR("client has neither application-identifier nor keychain-access-groups entitlements"));
1148 }
1149
1150 if (CFArrayContainsValue(accessGroups, CFRangeMake(0, ag_count), CFSTR("*"))) {
1151 /* Having the special accessGroup "*" allows access to all accessGroups. */
1152 accessGroups = NULL;
1153 }
1154
1155 Query *q = query_create_with_limit(query, client->musr, kSecMatchUnlimited, error);
1156 bool ok;
1157 if (q) {
1158 #if TARGET_OS_IPHONE
1159 if (q->q_system_keychain && client->inMultiUser) {
1160 CFReleaseNull(q->q_musrView);
1161 q->q_musrView = SecMUSRCopySystemKeychainUUID();
1162 } else {
1163 q->q_system_keychain = false;
1164 }
1165 #endif
1166
1167 query_set_caller_access_groups(q, accessGroups);
1168 /* Sanity check the query. */
1169 if (q->q_system_keychain && !client->allowSystemKeychain) {
1170 ok = SecError(errSecMissingEntitlement, error, CFSTR("client doesn't have entitlement for system keychain"));
1171 } else if (q->q_sync_bubble && !client->allowSyncBubbleKeychain) {
1172 ok = SecError(errSecMissingEntitlement, error, CFSTR("client doesn't have entitlement for syncbubble keychain"));
1173 } else if (q->q_limit != kSecMatchUnlimited) {
1174 ok = SecError(errSecMatchLimitUnsupported, error, CFSTR("match limit not supported by delete"));
1175 } else if (query_match_count(q) != 0) {
1176 ok = SecError(errSecItemMatchUnsupported, error, CFSTR("match not supported by delete"));
1177 } else if (q->q_ref) {
1178 ok = SecError(errSecValueRefUnsupported, error, CFSTR("value ref not supported by delete"));
1179 } else if (q->q_row_id && query_attr_count(q)) {
1180 ok = SecError(errSecItemIllegalQuery, error, CFSTR("rowid and other attributes are mutually exclusive"));
1181 } else {
1182 ok = kc_with_dbt(true, error, ^(SecDbConnectionRef dbt) {
1183 return kc_transaction(dbt, error, ^{
1184 return s3dl_query_delete(dbt, q, accessGroups, error);
1185 });
1186 });
1187 }
1188 ok = query_notify_and_destroy(q, ok, error);
1189 } else {
1190 ok = false;
1191 }
1192 return ok;
1193 }
1194
1195
1196 /* AUDIT[securityd](done):
1197 No caller provided inputs.
1198 */
1199 static bool
1200 SecItemServerDeleteAll(CFErrorRef *error) {
1201 return kc_with_dbt(true, error, ^bool (SecDbConnectionRef dbt) {
1202 return (kc_transaction(dbt, error, ^bool {
1203 return (SecDbExec(dbt, CFSTR("DELETE from genp;"), error) &&
1204 SecDbExec(dbt, CFSTR("DELETE from inet;"), error) &&
1205 SecDbExec(dbt, CFSTR("DELETE from cert;"), error) &&
1206 SecDbExec(dbt, CFSTR("DELETE from keys;"), error));
1207 }) && SecDbExec(dbt, CFSTR("VACUUM;"), error));
1208 });
1209 }
1210
1211 bool
1212 _SecItemDeleteAll(CFErrorRef *error) {
1213 return SecItemServerDeleteAll(error);
1214 }
1215
1216
1217 // MARK: -
1218 // MARK: Shared web credentials
1219
1220 /* constants */
1221 #define SEC_CONST_DECL(k,v) const CFStringRef k = CFSTR(v);
1222
1223 SEC_CONST_DECL (kSecSafariAccessGroup, "com.apple.cfnetwork");
1224 SEC_CONST_DECL (kSecSafariDefaultComment, "default");
1225 SEC_CONST_DECL (kSecSafariPasswordsNotSaved, "Passwords not saved");
1226 SEC_CONST_DECL (kSecSharedCredentialUrlScheme, "https://");
1227 SEC_CONST_DECL (kSecSharedWebCredentialsService, "webcredentials");
1228
1229 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1230 static dispatch_once_t sSecSWCInitializeOnce = 0;
1231 static void * sSecSWCLibrary = NULL;
1232 static SWCCheckService_f sSWCCheckService_f = NULL;
1233 static SWCSetServiceFlags_f sSWCSetServiceFlags_f = NULL;
1234
1235 static OSStatus _SecSWCEnsuredInitialized(void);
1236
1237 static OSStatus _SecSWCEnsuredInitialized(void)
1238 {
1239 __block OSStatus status = errSecNotAvailable;
1240
1241 dispatch_once(&sSecSWCInitializeOnce, ^{
1242 sSecSWCLibrary = dlopen("/System/Library/PrivateFrameworks/SharedWebCredentials.framework/SharedWebCredentials", RTLD_LAZY | RTLD_LOCAL);
1243 assert(sSecSWCLibrary);
1244 if (sSecSWCLibrary) {
1245 sSWCCheckService_f = (SWCCheckService_f)(uintptr_t) dlsym(sSecSWCLibrary, "SWCCheckService");
1246 sSWCSetServiceFlags_f = (SWCSetServiceFlags_f)(uintptr_t) dlsym(sSecSWCLibrary, "SWCSetServiceFlags");
1247 }
1248 });
1249
1250 if (sSWCCheckService_f && sSWCSetServiceFlags_f) {
1251 status = noErr;
1252 }
1253 return status;
1254 }
1255 #endif
1256
1257 #if !TARGET_IPHONE_SIMULATOR
1258 static SWCFlags
1259 _SecAppDomainApprovalStatus(CFStringRef appID, CFStringRef fqdn, CFErrorRef *error)
1260 {
1261 __block SWCFlags flags = kSWCFlags_None;
1262
1263 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1264 OSStatus status = _SecSWCEnsuredInitialized();
1265 if (status) {
1266 SecError(status, error, CFSTR("SWC initialize failed"));
1267 return flags;
1268 }
1269 CFRetainSafe(appID);
1270 CFRetainSafe(fqdn);
1271 dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
1272 dispatch_retain(semaphore);
1273 if (0 == sSWCCheckService_f(kSecSharedWebCredentialsService, appID, fqdn,
1274 ^void (OSStatus inStatus, SWCFlags inFlags, CFDictionaryRef inDetails) {
1275 if (!inStatus) { flags = inFlags; }
1276 CFReleaseSafe(appID);
1277 CFReleaseSafe(fqdn);
1278 dispatch_semaphore_signal(semaphore);
1279 dispatch_release(semaphore);
1280 //secerror("SWCCheckService: inStatus=%d, flags=%0X", inStatus, flags);
1281 }))
1282 {
1283 // wait for the block to complete, as we need its answer
1284 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
1285 }
1286 else // didn't queue the block
1287 {
1288 CFReleaseSafe(appID);
1289 CFReleaseSafe(fqdn);
1290 dispatch_release(semaphore);
1291 }
1292 dispatch_release(semaphore);
1293 #else
1294 flags |= (kSWCFlag_SiteApproved);
1295 #endif
1296
1297 if (!error) { return flags; }
1298 *error = NULL;
1299
1300 // check website approval status
1301 if (!(flags & kSWCFlag_SiteApproved)) {
1302 if (flags & kSWCFlag_Pending) {
1303 SecError(errSecAuthFailed, error, CFSTR("Approval is pending for \"%@\", try later"), fqdn);
1304 } else {
1305 SecError(errSecAuthFailed, error, CFSTR("\"%@\" failed to approve \"%@\""), fqdn, appID);
1306 }
1307 return flags;
1308 }
1309
1310 // check user approval status
1311 if (flags & kSWCFlag_UserDenied) {
1312 SecError(errSecAuthFailed, error, CFSTR("User denied access to \"%@\" by \"%@\""), fqdn, appID);
1313 }
1314 return flags;
1315 }
1316 #endif
1317
1318 #if !TARGET_IPHONE_SIMULATOR
1319 static bool
1320 _SecEntitlementContainsDomainForService(CFArrayRef domains, CFStringRef domain, CFStringRef service)
1321 {
1322 bool result = false;
1323 CFIndex idx, count = (domains) ? CFArrayGetCount(domains) : (CFIndex) 0;
1324 if (!count || !domain || !service) {
1325 return result;
1326 }
1327 for (idx=0; idx < count; idx++) {
1328 CFStringRef str = (CFStringRef) CFArrayGetValueAtIndex(domains, idx);
1329 if (str && CFStringHasPrefix(str, kSecSharedWebCredentialsService)) {
1330 CFIndex prefix_len = CFStringGetLength(kSecSharedWebCredentialsService)+1;
1331 CFIndex substr_len = CFStringGetLength(str) - prefix_len;
1332 CFRange range = { prefix_len, substr_len };
1333 CFStringRef substr = CFStringCreateWithSubstring(kCFAllocatorDefault, str, range);
1334 if (substr && CFEqual(substr, domain)) {
1335 result = true;
1336 }
1337 CFReleaseSafe(substr);
1338 if (result) {
1339 break;
1340 }
1341 }
1342 }
1343 return result;
1344 }
1345 #endif
1346
1347 static bool
1348 _SecAddNegativeWebCredential(SecurityClient *client, CFStringRef fqdn, CFStringRef appID, bool forSafari)
1349 {
1350 bool result = false;
1351 if (!fqdn) { return result; }
1352
1353 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1354 OSStatus status = _SecSWCEnsuredInitialized();
1355 if (status) { return false; }
1356
1357 // update our database
1358 CFRetainSafe(appID);
1359 CFRetainSafe(fqdn);
1360 if (0 == sSWCSetServiceFlags_f(kSecSharedWebCredentialsService,
1361 appID, fqdn, kSWCFlag_ExternalMask, kSWCFlag_UserDenied,
1362 ^void(OSStatus inStatus, SWCFlags inNewFlags){
1363 CFReleaseSafe(appID);
1364 CFReleaseSafe(fqdn);
1365 }))
1366 {
1367 result = true;
1368 }
1369 else // didn't queue the block
1370 {
1371 CFReleaseSafe(appID);
1372 CFReleaseSafe(fqdn);
1373 }
1374 #endif
1375 if (!forSafari) { return result; }
1376
1377 // below this point: create a negative Safari web credential item
1378
1379 CFMutableDictionaryRef attrs = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1380 if (!attrs) { return result; }
1381
1382 CFErrorRef error = NULL;
1383 CFStringRef accessGroup = CFSTR("*");
1384 SecurityClient swcclient = {
1385 .task = NULL,
1386 .accessGroups = CFArrayCreate(kCFAllocatorDefault, (const void **)&accessGroup, 1, &kCFTypeArrayCallBacks),
1387 .allowSystemKeychain = false,
1388 .allowSyncBubbleKeychain = false,
1389 .isNetworkExtension = false,
1390 .musr = client->musr,
1391 };
1392
1393 CFDictionaryAddValue(attrs, kSecClass, kSecClassInternetPassword);
1394 CFDictionaryAddValue(attrs, kSecAttrAccessGroup, kSecSafariAccessGroup);
1395 CFDictionaryAddValue(attrs, kSecAttrAuthenticationType, kSecAttrAuthenticationTypeHTMLForm);
1396 CFDictionaryAddValue(attrs, kSecAttrProtocol, kSecAttrProtocolHTTPS);
1397 CFDictionaryAddValue(attrs, kSecAttrServer, fqdn);
1398 CFDictionaryAddValue(attrs, kSecAttrSynchronizable, kCFBooleanTrue);
1399
1400 (void)_SecItemDelete(attrs, &swcclient, &error);
1401 CFReleaseNull(error);
1402
1403 CFDictionaryAddValue(attrs, kSecAttrAccount, kSecSafariPasswordsNotSaved);
1404 CFDictionaryAddValue(attrs, kSecAttrComment, kSecSafariDefaultComment);
1405
1406 CFStringRef label = CFStringCreateWithFormat(kCFAllocatorDefault,
1407 NULL, CFSTR("%@ (%@)"), fqdn, kSecSafariPasswordsNotSaved);
1408 if (label) {
1409 CFDictionaryAddValue(attrs, kSecAttrLabel, label);
1410 CFReleaseSafe(label);
1411 }
1412
1413 UInt8 space = ' ';
1414 CFDataRef data = CFDataCreate(kCFAllocatorDefault, &space, 1);
1415 if (data) {
1416 CFDictionarySetValue(attrs, kSecValueData, data);
1417 CFReleaseSafe(data);
1418 }
1419
1420 CFTypeRef addResult = NULL;
1421 result = _SecItemAdd(attrs, &swcclient, &addResult, &error);
1422
1423 CFReleaseSafe(addResult);
1424 CFReleaseSafe(error);
1425 CFReleaseSafe(attrs);
1426 CFReleaseSafe(swcclient.accessGroups);
1427
1428 return result;
1429 }
1430
1431 /* Specialized version of SecItemAdd for shared web credentials */
1432 bool
1433 _SecAddSharedWebCredential(CFDictionaryRef attributes,
1434 SecurityClient *client,
1435 const audit_token_t *clientAuditToken,
1436 CFStringRef appID,
1437 CFArrayRef domains,
1438 CFTypeRef *result,
1439 CFErrorRef *error)
1440 {
1441
1442 SecurityClient swcclient = {};
1443
1444 CFStringRef fqdn = CFRetainSafe(CFDictionaryGetValue(attributes, kSecAttrServer));
1445 CFStringRef account = CFDictionaryGetValue(attributes, kSecAttrAccount);
1446 CFStringRef password = CFDictionaryGetValue(attributes, CFSTR("spwd") /* kSecSharedPassword */);
1447 CFStringRef accessGroup = CFSTR("*");
1448 CFMutableDictionaryRef query = NULL, attrs = NULL;
1449 SInt32 port = -1;
1450 bool ok = false;
1451
1452 // check autofill enabled status
1453 if (!swca_autofill_enabled(clientAuditToken)) {
1454 SecError(errSecBadReq, error, CFSTR("Autofill is not enabled in Safari settings"));
1455 goto cleanup;
1456 }
1457
1458 // parse fqdn with CFURL here, since it could be specified as domain:port
1459 if (fqdn) {
1460 CFStringRef urlStr = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@%@"), kSecSharedCredentialUrlScheme, fqdn);
1461 if (urlStr) {
1462 CFURLRef url = CFURLCreateWithString(kCFAllocatorDefault, urlStr, nil);
1463 if (url) {
1464 CFStringRef hostname = CFURLCopyHostName(url);
1465 if (hostname) {
1466 CFReleaseSafe(fqdn);
1467 fqdn = hostname;
1468 port = CFURLGetPortNumber(url);
1469 }
1470 CFReleaseSafe(url);
1471 }
1472 CFReleaseSafe(urlStr);
1473 }
1474 }
1475
1476 if (!account) {
1477 SecError(errSecParam, error, CFSTR("No account provided"));
1478 goto cleanup;
1479 }
1480 if (!fqdn) {
1481 SecError(errSecParam, error, CFSTR("No domain provided"));
1482 goto cleanup;
1483 }
1484
1485 #if TARGET_IPHONE_SIMULATOR
1486 secerror("app/site association entitlements not checked in Simulator");
1487 #else
1488 OSStatus status = errSecMissingEntitlement;
1489 // validate that fqdn is part of caller's shared credential domains entitlement
1490 if (!appID) {
1491 SecError(status, error, CFSTR("Missing application-identifier entitlement"));
1492 goto cleanup;
1493 }
1494 if (_SecEntitlementContainsDomainForService(domains, fqdn, kSecSharedWebCredentialsService)) {
1495 status = errSecSuccess;
1496 }
1497 if (errSecSuccess != status) {
1498 CFStringRef msg = CFStringCreateWithFormat(kCFAllocatorDefault, NULL,
1499 CFSTR("%@ not found in %@ entitlement"), fqdn, kSecEntitlementAssociatedDomains);
1500 if (!msg) {
1501 msg = CFRetain(CFSTR("Requested domain not found in entitlement"));
1502 }
1503 SecError(status, error, CFSTR("%@"), msg);
1504 CFReleaseSafe(msg);
1505 goto cleanup;
1506 }
1507 #endif
1508
1509 #if TARGET_IPHONE_SIMULATOR
1510 secerror("Ignoring app/site approval state in the Simulator.");
1511 #else
1512 // get approval status for this app/domain pair
1513 SWCFlags flags = _SecAppDomainApprovalStatus(appID, fqdn, error);
1514 if (!(flags & kSWCFlag_SiteApproved)) {
1515 goto cleanup;
1516 }
1517 #endif
1518
1519 // give ourselves access to see matching items for kSecSafariAccessGroup
1520 swcclient.task = NULL;
1521 swcclient.accessGroups = CFArrayCreate(kCFAllocatorDefault, (const void **)&accessGroup, 1, &kCFTypeArrayCallBacks);
1522 swcclient.allowSystemKeychain = false;
1523 swcclient.musr = client->musr;
1524 swcclient.allowSystemKeychain = false;
1525 swcclient.allowSyncBubbleKeychain = false;
1526 swcclient.isNetworkExtension = false;
1527
1528
1529 // create lookup query
1530 query = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1531 if (!query) {
1532 SecError(errSecAllocate, error, CFSTR("Unable to create query dictionary"));
1533 goto cleanup;
1534 }
1535 CFDictionaryAddValue(query, kSecClass, kSecClassInternetPassword);
1536 CFDictionaryAddValue(query, kSecAttrAccessGroup, kSecSafariAccessGroup);
1537 CFDictionaryAddValue(query, kSecAttrAuthenticationType, kSecAttrAuthenticationTypeHTMLForm);
1538 CFDictionaryAddValue(query, kSecAttrServer, fqdn);
1539 CFDictionaryAddValue(query, kSecAttrSynchronizable, kCFBooleanTrue);
1540
1541 // check for presence of Safari's negative entry ('passwords not saved')
1542 CFDictionarySetValue(query, kSecAttrAccount, kSecSafariPasswordsNotSaved);
1543 ok = _SecItemCopyMatching(query, &swcclient, result, error);
1544 if(result) CFReleaseNull(*result);
1545 CFReleaseNull(*error);
1546 if (ok) {
1547 SecError(errSecDuplicateItem, error, CFSTR("Item already exists for this server"));
1548 goto cleanup;
1549 }
1550
1551 // now use the provided account (and optional port number, if one was present)
1552 CFDictionarySetValue(query, kSecAttrAccount, account);
1553 if (port < -1 || port > 0) {
1554 SInt16 portValueShort = (port & 0xFFFF);
1555 CFNumberRef portNumber = CFNumberCreate(NULL, kCFNumberSInt16Type, &portValueShort);
1556 CFDictionaryAddValue(query, kSecAttrPort, portNumber);
1557 CFReleaseSafe(portNumber);
1558 }
1559
1560 // look up existing password
1561 if (_SecItemCopyMatching(query, &swcclient, result, error)) {
1562 // found it, so this becomes either an "update password" or "delete password" operation
1563 if(result) CFReleaseNull(*result);
1564 CFReleaseNull(*error);
1565 bool update = (password != NULL);
1566 if (update) {
1567 attrs = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1568 CFDataRef credential = CFStringCreateExternalRepresentation(kCFAllocatorDefault, password, kCFStringEncodingUTF8, 0);
1569 CFDictionaryAddValue(attrs, kSecValueData, credential);
1570 CFReleaseSafe(credential);
1571 CFDictionaryAddValue(attrs, kSecAttrComment, kSecSafariDefaultComment);
1572
1573 // confirm the update
1574 // (per rdar://16676310 we always prompt, even if there was prior user approval)
1575 ok = /*approved ||*/ swca_confirm_operation(swca_update_request_id, clientAuditToken, query, error,
1576 ^void (CFStringRef fqdn) { _SecAddNegativeWebCredential(client, fqdn, appID, false); });
1577 if (ok) {
1578 ok = _SecItemUpdate(query, attrs, &swcclient, error);
1579 }
1580 }
1581 else {
1582 // confirm the delete
1583 // (per rdar://16676288 we always prompt, even if there was prior user approval)
1584 ok = /*approved ||*/ swca_confirm_operation(swca_delete_request_id, clientAuditToken, query, error,
1585 ^void (CFStringRef fqdn) { _SecAddNegativeWebCredential(client, fqdn, appID, false); });
1586 if (ok) {
1587 ok = _SecItemDelete(query, &swcclient, error);
1588 }
1589 }
1590 if (ok) {
1591 CFReleaseNull(*error);
1592 }
1593 goto cleanup;
1594 }
1595 if(result) CFReleaseNull(*result);
1596 CFReleaseNull(*error);
1597
1598 // password does not exist, so prepare to add it
1599 if (!password) {
1600 // a NULL password value removes the existing credential. Since we didn't find it, this is a no-op.
1601 ok = true;
1602 goto cleanup;
1603 }
1604 else {
1605 CFStringRef label = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@ (%@)"), fqdn, account);
1606 if (label) {
1607 CFDictionaryAddValue(query, kSecAttrLabel, label);
1608 CFReleaseSafe(label);
1609 }
1610 // NOTE: we always expect to use HTTPS for web forms.
1611 CFDictionaryAddValue(query, kSecAttrProtocol, kSecAttrProtocolHTTPS);
1612
1613 CFDataRef credential = CFStringCreateExternalRepresentation(kCFAllocatorDefault, password, kCFStringEncodingUTF8, 0);
1614 CFDictionarySetValue(query, kSecValueData, credential);
1615 CFReleaseSafe(credential);
1616 CFDictionarySetValue(query, kSecAttrComment, kSecSafariDefaultComment);
1617
1618 CFReleaseSafe(swcclient.accessGroups);
1619 swcclient.accessGroups = CFArrayCreate(kCFAllocatorDefault, (const void **)&kSecSafariAccessGroup, 1, &kCFTypeArrayCallBacks);
1620
1621 // mark the item as created by this function
1622 const int32_t creator_value = 'swca';
1623 CFNumberRef creator = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &creator_value);
1624 if (creator) {
1625 CFDictionarySetValue(query, kSecAttrCreator, creator);
1626 CFReleaseSafe(creator);
1627 ok = true;
1628 }
1629 else {
1630 // confirm the add
1631 // (per rdar://16680019, we won't prompt here in the normal case)
1632 ok = /*approved ||*/ swca_confirm_operation(swca_add_request_id, clientAuditToken, query, error,
1633 ^void (CFStringRef fqdn) { _SecAddNegativeWebCredential(client, fqdn, appID, false); });
1634 }
1635 }
1636 if (ok) {
1637 ok = _SecItemAdd(query, &swcclient, result, error);
1638 }
1639
1640 cleanup:
1641 CFReleaseSafe(attrs);
1642 CFReleaseSafe(query);
1643 CFReleaseSafe(swcclient.accessGroups);
1644 CFReleaseSafe(fqdn);
1645 return ok;
1646 }
1647
1648 /* Specialized version of SecItemCopyMatching for shared web credentials */
1649 bool
1650 _SecCopySharedWebCredential(CFDictionaryRef query,
1651 SecurityClient *client,
1652 const audit_token_t *clientAuditToken,
1653 CFStringRef appID,
1654 CFArrayRef domains,
1655 CFTypeRef *result,
1656 CFErrorRef *error)
1657 {
1658 CFMutableArrayRef credentials = NULL;
1659 CFMutableArrayRef foundItems = NULL;
1660 CFMutableArrayRef fqdns = NULL;
1661 CFStringRef fqdn = NULL;
1662 CFStringRef account = NULL;
1663 CFIndex idx, count;
1664 SInt32 port = -1;
1665 bool ok = false;
1666
1667 require_quiet(result, cleanup);
1668 credentials = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
1669 foundItems = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
1670 fqdns = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
1671
1672 // give ourselves access to see matching items for kSecSafariAccessGroup
1673 CFStringRef accessGroup = CFSTR("*");
1674 SecurityClient swcclient = {
1675 .task = NULL,
1676 .accessGroups = CFArrayCreate(kCFAllocatorDefault, (const void **)&accessGroup, 1, &kCFTypeArrayCallBacks),
1677 .allowSystemKeychain = false,
1678 .allowSyncBubbleKeychain = false,
1679 .isNetworkExtension = false,
1680 .musr = client->musr,
1681 };
1682
1683 // On input, the query dictionary contains optional fqdn and account entries.
1684 fqdn = CFDictionaryGetValue(query, kSecAttrServer);
1685 account = CFDictionaryGetValue(query, kSecAttrAccount);
1686
1687 // Check autofill enabled status
1688 if (!swca_autofill_enabled(clientAuditToken)) {
1689 SecError(errSecBadReq, error, CFSTR("Autofill is not enabled in Safari settings"));
1690 goto cleanup;
1691 }
1692
1693 // Check fqdn; if NULL, add domains from caller's entitlement.
1694 if (fqdn) {
1695 CFArrayAppendValue(fqdns, fqdn);
1696 }
1697 else if (domains) {
1698 CFIndex idx, count = CFArrayGetCount(domains);
1699 for (idx=0; idx < count; idx++) {
1700 CFStringRef str = (CFStringRef) CFArrayGetValueAtIndex(domains, idx);
1701 // Parse the entry for our service label prefix
1702 if (str && CFStringHasPrefix(str, kSecSharedWebCredentialsService)) {
1703 CFIndex prefix_len = CFStringGetLength(kSecSharedWebCredentialsService)+1;
1704 CFIndex substr_len = CFStringGetLength(str) - prefix_len;
1705 CFRange range = { prefix_len, substr_len };
1706 fqdn = CFStringCreateWithSubstring(kCFAllocatorDefault, str, range);
1707 if (fqdn) {
1708 CFArrayAppendValue(fqdns, fqdn);
1709 CFRelease(fqdn);
1710 }
1711 }
1712 }
1713 }
1714 count = CFArrayGetCount(fqdns);
1715 if (count < 1) {
1716 SecError(errSecParam, error, CFSTR("No domain provided"));
1717 goto cleanup;
1718 }
1719
1720 // Aggregate search results for each domain
1721 for (idx = 0; idx < count; idx++) {
1722 CFMutableArrayRef items = NULL;
1723 CFMutableDictionaryRef attrs = NULL;
1724 fqdn = (CFStringRef) CFArrayGetValueAtIndex(fqdns, idx);
1725 CFRetainSafe(fqdn);
1726 port = -1;
1727
1728 // Parse the fqdn for a possible port specifier.
1729 if (fqdn) {
1730 CFStringRef urlStr = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@%@"), kSecSharedCredentialUrlScheme, fqdn);
1731 if (urlStr) {
1732 CFURLRef url = CFURLCreateWithString(kCFAllocatorDefault, urlStr, nil);
1733 if (url) {
1734 CFStringRef hostname = CFURLCopyHostName(url);
1735 if (hostname) {
1736 CFReleaseSafe(fqdn);
1737 fqdn = hostname;
1738 port = CFURLGetPortNumber(url);
1739 }
1740 CFReleaseSafe(url);
1741 }
1742 CFReleaseSafe(urlStr);
1743 }
1744 }
1745
1746 #if TARGET_IPHONE_SIMULATOR
1747 secerror("app/site association entitlements not checked in Simulator");
1748 #else
1749 OSStatus status = errSecMissingEntitlement;
1750 if (!appID) {
1751 SecError(status, error, CFSTR("Missing application-identifier entitlement"));
1752 CFReleaseSafe(fqdn);
1753 goto cleanup;
1754 }
1755 // validate that fqdn is part of caller's entitlement
1756 if (_SecEntitlementContainsDomainForService(domains, fqdn, kSecSharedWebCredentialsService)) {
1757 status = errSecSuccess;
1758 }
1759 if (errSecSuccess != status) {
1760 CFStringRef msg = CFStringCreateWithFormat(kCFAllocatorDefault, NULL,
1761 CFSTR("%@ not found in %@ entitlement"), fqdn, kSecEntitlementAssociatedDomains);
1762 if (!msg) {
1763 msg = CFRetain(CFSTR("Requested domain not found in entitlement"));
1764 }
1765 SecError(status, error, CFSTR("%@"), msg);
1766 CFReleaseSafe(msg);
1767 CFReleaseSafe(fqdn);
1768 goto cleanup;
1769 }
1770 #endif
1771
1772 attrs = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1773 if (!attrs) {
1774 SecError(errSecAllocate, error, CFSTR("Unable to create query dictionary"));
1775 CFReleaseSafe(fqdn);
1776 goto cleanup;
1777 }
1778 CFDictionaryAddValue(attrs, kSecClass, kSecClassInternetPassword);
1779 CFDictionaryAddValue(attrs, kSecAttrAccessGroup, kSecSafariAccessGroup);
1780 CFDictionaryAddValue(attrs, kSecAttrAuthenticationType, kSecAttrAuthenticationTypeHTMLForm);
1781 CFDictionaryAddValue(attrs, kSecAttrServer, fqdn);
1782 if (account) {
1783 CFDictionaryAddValue(attrs, kSecAttrAccount, account);
1784 }
1785 if (port < -1 || port > 0) {
1786 SInt16 portValueShort = (port & 0xFFFF);
1787 CFNumberRef portNumber = CFNumberCreate(NULL, kCFNumberSInt16Type, &portValueShort);
1788 CFDictionaryAddValue(attrs, kSecAttrPort, portNumber);
1789 CFReleaseSafe(portNumber);
1790 }
1791 CFDictionaryAddValue(attrs, kSecAttrSynchronizable, kCFBooleanTrue);
1792 CFDictionaryAddValue(attrs, kSecMatchLimit, kSecMatchLimitAll);
1793 CFDictionaryAddValue(attrs, kSecReturnAttributes, kCFBooleanTrue);
1794 CFDictionaryAddValue(attrs, kSecReturnData, kCFBooleanTrue);
1795
1796 ok = _SecItemCopyMatching(attrs, &swcclient, (CFTypeRef*)&items, error);
1797 if (count > 1) {
1798 // ignore interim error since we have multiple domains to search
1799 CFReleaseNull(*error);
1800 }
1801 if (ok && items && CFGetTypeID(items) == CFArrayGetTypeID()) {
1802 #if TARGET_IPHONE_SIMULATOR
1803 secerror("Ignoring app/site approval state in the Simulator.");
1804 bool approved = true;
1805 #else
1806 // get approval status for this app/domain pair
1807 SWCFlags flags = _SecAppDomainApprovalStatus(appID, fqdn, error);
1808 if (count > 1) {
1809 // ignore interim error since we have multiple domains to check
1810 CFReleaseNull(*error);
1811 }
1812 bool approved = (flags & kSWCFlag_SiteApproved);
1813 #endif
1814 if (approved) {
1815 CFArrayAppendArray(foundItems, items, CFRangeMake(0, CFArrayGetCount(items)));
1816 }
1817 }
1818 CFReleaseSafe(items);
1819 CFReleaseSafe(attrs);
1820 CFReleaseSafe(fqdn);
1821 }
1822
1823 // If matching credentials are found, the credentials provided to the completionHandler
1824 // will be a CFArrayRef containing CFDictionaryRef entries. Each dictionary entry will
1825 // contain the following pairs (see Security/SecItem.h):
1826 // key: kSecAttrServer value: CFStringRef (the website)
1827 // key: kSecAttrAccount value: CFStringRef (the account)
1828 // key: kSecSharedPassword value: CFStringRef (the password)
1829 // Optional keys:
1830 // key: kSecAttrPort value: CFNumberRef (the port number, if non-standard for https)
1831
1832 count = CFArrayGetCount(foundItems);
1833 for (idx = 0; idx < count; idx++) {
1834 CFDictionaryRef dict = (CFDictionaryRef) CFArrayGetValueAtIndex(foundItems, idx);
1835 CFMutableDictionaryRef newdict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1836 if (newdict && dict && CFGetTypeID(dict) == CFDictionaryGetTypeID()) {
1837 CFStringRef srvr = CFDictionaryGetValue(dict, kSecAttrServer);
1838 CFStringRef acct = CFDictionaryGetValue(dict, kSecAttrAccount);
1839 CFNumberRef pnum = CFDictionaryGetValue(dict, kSecAttrPort);
1840 CFStringRef icmt = CFDictionaryGetValue(dict, kSecAttrComment);
1841 CFDataRef data = CFDictionaryGetValue(dict, kSecValueData);
1842 if (srvr) {
1843 CFDictionaryAddValue(newdict, kSecAttrServer, srvr);
1844 }
1845 if (acct) {
1846 CFDictionaryAddValue(newdict, kSecAttrAccount, acct);
1847 }
1848 if (pnum) {
1849 SInt16 pval = -1;
1850 if (CFNumberGetValue(pnum, kCFNumberSInt16Type, &pval) &&
1851 (pval < -1 || pval > 0)) {
1852 CFDictionaryAddValue(newdict, kSecAttrPort, pnum);
1853 }
1854 }
1855 if (data) {
1856 CFStringRef password = CFStringCreateFromExternalRepresentation(kCFAllocatorDefault, data, kCFStringEncodingUTF8);
1857 if (password) {
1858 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1859 CFDictionaryAddValue(newdict, kSecSharedPassword, password);
1860 #else
1861 CFDictionaryAddValue(newdict, CFSTR("spwd"), password);
1862 #endif
1863 CFReleaseSafe(password);
1864 }
1865 }
1866 if (icmt && CFEqual(icmt, kSecSafariDefaultComment)) {
1867 CFArrayInsertValueAtIndex(credentials, 0, newdict);
1868 } else {
1869 CFArrayAppendValue(credentials, newdict);
1870 }
1871 }
1872 CFReleaseSafe(newdict);
1873 }
1874
1875 if (count) {
1876
1877 ok = false;
1878
1879 // create a new array of dictionaries (without the actual password) for picker UI
1880 count = CFArrayGetCount(credentials);
1881 CFMutableArrayRef items = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
1882 for (idx = 0; idx < count; idx++) {
1883 CFDictionaryRef dict = (CFDictionaryRef) CFArrayGetValueAtIndex(credentials, idx);
1884 CFMutableDictionaryRef newdict = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, dict);
1885 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1886 CFDictionaryRemoveValue(newdict, kSecSharedPassword);
1887 #else
1888 CFDictionaryRemoveValue(newdict, CFSTR("spwd"));
1889 #endif
1890 CFArrayAppendValue(items, newdict);
1891 CFReleaseSafe(newdict);
1892 }
1893
1894 // prompt user to select one of the dictionary items
1895 CFDictionaryRef selected = swca_copy_selected_dictionary(swca_select_request_id,
1896 clientAuditToken, items, error);
1897 if (selected) {
1898 // find the matching item in our credentials array
1899 CFStringRef srvr = CFDictionaryGetValue(selected, kSecAttrServer);
1900 CFStringRef acct = CFDictionaryGetValue(selected, kSecAttrAccount);
1901 CFNumberRef pnum = CFDictionaryGetValue(selected, kSecAttrPort);
1902 for (idx = 0; idx < count; idx++) {
1903 CFDictionaryRef dict = (CFDictionaryRef) CFArrayGetValueAtIndex(credentials, idx);
1904 CFStringRef srvr1 = CFDictionaryGetValue(dict, kSecAttrServer);
1905 CFStringRef acct1 = CFDictionaryGetValue(dict, kSecAttrAccount);
1906 CFNumberRef pnum1 = CFDictionaryGetValue(dict, kSecAttrPort);
1907
1908 if (!srvr || !srvr1 || !CFEqual(srvr, srvr1)) continue;
1909 if (!acct || !acct1 || !CFEqual(acct, acct1)) continue;
1910 if ((pnum && pnum1) && !CFEqual(pnum, pnum1)) continue;
1911
1912 // we have a match!
1913 CFReleaseSafe(selected);
1914 CFRetainSafe(dict);
1915 selected = dict;
1916 ok = true;
1917 break;
1918 }
1919 }
1920 CFReleaseSafe(items);
1921 CFArrayRemoveAllValues(credentials);
1922 if (selected && ok) {
1923 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1924 fqdn = CFDictionaryGetValue(selected, kSecAttrServer);
1925 #endif
1926 CFArrayAppendValue(credentials, selected);
1927 }
1928
1929 if (ok) {
1930 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1931 // register confirmation with database
1932 OSStatus status = _SecSWCEnsuredInitialized();
1933 if (status) {
1934 SecError(status, error, CFSTR("SWC initialize failed"));
1935 ok = false;
1936 CFReleaseSafe(selected);
1937 goto cleanup;
1938 }
1939 CFRetainSafe(appID);
1940 CFRetainSafe(fqdn);
1941 if (0 != sSWCSetServiceFlags_f(kSecSharedWebCredentialsService,
1942 appID, fqdn, kSWCFlag_ExternalMask, kSWCFlag_UserApproved,
1943 ^void(OSStatus inStatus, SWCFlags inNewFlags){
1944 CFReleaseSafe(appID);
1945 CFReleaseSafe(fqdn);
1946 }))
1947 {
1948 // we didn't queue the block
1949 CFReleaseSafe(appID);
1950 CFReleaseSafe(fqdn);
1951 }
1952 #endif
1953 }
1954 CFReleaseSafe(selected);
1955 }
1956 else if (NULL == *error) {
1957 // found no items, and we haven't already filled in the error
1958 SecError(errSecItemNotFound, error, CFSTR("no matching items found"));
1959 }
1960
1961 cleanup:
1962 if (!ok) {
1963 CFArrayRemoveAllValues(credentials);
1964 }
1965 CFReleaseSafe(foundItems);
1966 *result = credentials;
1967 CFReleaseSafe(swcclient.accessGroups);
1968 CFReleaseSafe(fqdns);
1969
1970 return ok;
1971 }
1972
1973 // MARK: -
1974 // MARK: Keychain backup
1975
1976 CF_RETURNS_RETAINED CFDataRef
1977 _SecServerKeychainCreateBackup(SecurityClient *client, CFDataRef keybag, CFDataRef passcode, CFErrorRef *error) {
1978 CFDataRef backup;
1979 SecDbConnectionRef dbt = SecDbConnectionAquire(kc_dbhandle(), false, error);
1980
1981 if (!dbt)
1982 return NULL;
1983
1984 if (keybag == NULL && passcode == NULL) {
1985 #if USE_KEYSTORE
1986 backup = SecServerExportBackupableKeychain(dbt, client, KEYBAG_DEVICE, backup_keybag_handle, error);
1987 #else /* !USE_KEYSTORE */
1988 (void)client;
1989 SecError(errSecParam, error, CFSTR("Why are you doing this?"));
1990 backup = NULL;
1991 #endif /* USE_KEYSTORE */
1992 } else {
1993 backup = SecServerKeychainCreateBackup(dbt, client, keybag, passcode, error);
1994 }
1995
1996 SecDbConnectionRelease(dbt);
1997
1998 return backup;
1999 }
2000
2001 bool
2002 _SecServerKeychainRestore(CFDataRef backup, SecurityClient *client, CFDataRef keybag, CFDataRef passcode, CFErrorRef *error) {
2003 if (backup == NULL || keybag == NULL)
2004 return SecError(errSecParam, error, CFSTR("backup or keybag missing"));
2005
2006 __block bool ok = true;
2007 ok &= SecDbPerformWrite(kc_dbhandle(), error, ^(SecDbConnectionRef dbconn) {
2008 ok = SecServerKeychainRestore(dbconn, client, backup, keybag, passcode, error);
2009 });
2010
2011 if (ok) {
2012 SecKeychainChanged(true);
2013 }
2014
2015 return ok;
2016 }
2017
2018
2019 // MARK: -
2020 // MARK: SecItemDataSource
2021
2022 // Make sure to call this before any writes to the keychain, so that we fire
2023 // up the engines to monitor manifest changes.
2024 SOSDataSourceFactoryRef SecItemDataSourceFactoryGetDefault(void) {
2025 return SecItemDataSourceFactoryGetShared(kc_dbhandle());
2026 }
2027
2028 /* AUDIT[securityd]:
2029 args_in (ok) is a caller provided, CFDictionaryRef.
2030 */
2031
2032 CF_RETURNS_RETAINED CFArrayRef
2033 _SecServerKeychainSyncUpdateMessage(CFDictionaryRef updates, CFErrorRef *error) {
2034 // This never fails, trust us!
2035 return SOSCCHandleUpdateMessage(updates);
2036 }
2037
2038 //
2039 // Truthiness in the cloud backup/restore support.
2040 //
2041
2042 static CFDictionaryRef
2043 _SecServerCopyTruthInTheCloud(CFDataRef keybag, CFDataRef password,
2044 CFDictionaryRef backup, CFErrorRef *error)
2045 {
2046 SOSManifestRef mold = NULL, mnow = NULL, mdelete = NULL, madd = NULL;
2047 __block CFMutableDictionaryRef backup_new = NULL;
2048 keybag_handle_t bag_handle;
2049 if (!ks_open_keybag(keybag, password, &bag_handle, error))
2050 return backup_new;
2051
2052 // We need to have a datasource singleton for protection domain
2053 // kSecAttrAccessibleWhenUnlocked and keep a single shared engine
2054 // instance around which we create in the datasource constructor as well.
2055 SOSDataSourceFactoryRef dsf = SecItemDataSourceFactoryGetDefault();
2056 SOSDataSourceRef ds = SOSDataSourceFactoryCreateDataSource(dsf, kSecAttrAccessibleWhenUnlocked, error);
2057 if (ds) {
2058 backup_new = backup ? CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, backup) : CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2059 mold = SOSCreateManifestWithBackup(backup, error);
2060 SOSEngineRef engine = SOSDataSourceGetSharedEngine(ds, error);
2061 mnow = SOSEngineCopyManifest(engine, NULL);
2062 if (!mnow) {
2063 mnow = SOSDataSourceCopyManifestWithViewNameSet(ds, SOSViewsGetV0ViewSet(), error);
2064 }
2065 if (!mnow) {
2066 CFReleaseNull(backup_new);
2067 secerror("failed to obtain manifest for keychain: %@", error ? *error : NULL);
2068 } else {
2069 SOSManifestDiff(mold, mnow, &mdelete, &madd, error);
2070 }
2071
2072 // Delete everything from the new_backup that is no longer in the datasource according to the datasources manifest.
2073 SOSManifestForEach(mdelete, ^(CFDataRef digest_data, bool *stop) {
2074 CFStringRef deleted_item_key = CFDataCopyHexString(digest_data);
2075 CFDictionaryRemoveValue(backup_new, deleted_item_key);
2076 CFRelease(deleted_item_key);
2077 });
2078
2079 CFMutableArrayRef changes = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
2080 SOSDataSourceForEachObject(ds, madd, error, ^void(CFDataRef digest, SOSObjectRef object, bool *stop) {
2081 CFErrorRef localError = NULL;
2082 CFDataRef digest_data = NULL;
2083 CFTypeRef value = NULL;
2084 if (!object) {
2085 // Key in our manifest can't be found in db, remove it from our manifest
2086 SOSChangesAppendDelete(changes, digest);
2087 } else if (!(digest_data = SOSObjectCopyDigest(ds, object, &localError))
2088 || !(value = SOSObjectCopyBackup(ds, object, bag_handle, &localError))) {
2089 if (SecErrorGetOSStatus(localError) == errSecDecode) {
2090 // Ignore decode errors, pretend the objects aren't there
2091 CFRelease(localError);
2092 // Object undecodable, remove it from our manifest
2093 SOSChangesAppendDelete(changes, digest);
2094 } else {
2095 // Stop iterating and propagate out all other errors.
2096 *stop = true;
2097 *error = localError;
2098 CFReleaseNull(backup_new);
2099 }
2100 } else {
2101 // TODO: Should we skip tombstones here?
2102 CFStringRef key = CFDataCopyHexString(digest_data);
2103 CFDictionarySetValue(backup_new, key, value);
2104 CFReleaseSafe(key);
2105 }
2106 CFReleaseSafe(digest_data);
2107 CFReleaseSafe(value);
2108 }) || CFReleaseNull(backup_new);
2109
2110 if (CFArrayGetCount(changes)) {
2111 if (!SOSEngineUpdateChanges(engine, kSOSDataSourceSOSTransaction, changes, error)) {
2112 CFReleaseNull(backup_new);
2113 }
2114 }
2115 CFReleaseSafe(changes);
2116
2117 SOSDataSourceRelease(ds, error) || CFReleaseNull(backup_new);
2118 }
2119
2120 CFReleaseSafe(mold);
2121 CFReleaseSafe(mnow);
2122 CFReleaseSafe(madd);
2123 CFReleaseSafe(mdelete);
2124 ks_close_keybag(bag_handle, error) || CFReleaseNull(backup_new);
2125
2126 return backup_new;
2127 }
2128
2129 static bool
2130 _SecServerRestoreTruthInTheCloud(CFDataRef keybag, CFDataRef password, CFDictionaryRef backup_in, CFErrorRef *error) {
2131 __block bool ok = true;
2132 keybag_handle_t bag_handle;
2133 if (!ks_open_keybag(keybag, password, &bag_handle, error))
2134 return false;
2135
2136 SOSManifestRef mbackup = SOSCreateManifestWithBackup(backup_in, error);
2137 if (mbackup) {
2138 SOSDataSourceFactoryRef dsf = SecItemDataSourceFactoryGetDefault();
2139 SOSDataSourceRef ds = SOSDataSourceFactoryCreateDataSource(dsf, kSecAttrAccessibleWhenUnlocked, error);
2140 ok &= ds && SOSDataSourceWith(ds, error, ^(SOSTransactionRef txn, bool *commit) {
2141 SOSManifestRef mnow = SOSDataSourceCopyManifestWithViewNameSet(ds, SOSViewsGetV0BackupViewSet(), error);
2142 SOSManifestRef mdelete = NULL, madd = NULL;
2143 SOSManifestDiff(mnow, mbackup, &mdelete, &madd, error);
2144
2145 // Don't delete everything in datasource not in backup.
2146
2147 // Add items from the backup
2148 SOSManifestForEach(madd, ^void(CFDataRef e, bool *stop) {
2149 CFDictionaryRef item = NULL;
2150 CFStringRef sha1 = CFDataCopyHexString(e);
2151 if (sha1) {
2152 item = CFDictionaryGetValue(backup_in, sha1);
2153 CFRelease(sha1);
2154 }
2155 if (item) {
2156 CFErrorRef localError = NULL;
2157
2158 if (!SOSObjectRestoreObject(ds, txn, bag_handle, item, &localError)) {
2159 OSStatus status = SecErrorGetOSStatus(localError);
2160 if (status == errSecDuplicateItem) {
2161 // Log and ignore duplicate item errors during restore
2162 secnotice("titc", "restore %@ not replacing existing item", item);
2163 } else if (status == errSecDecode) {
2164 // Log and ignore corrupted item errors during restore
2165 secnotice("titc", "restore %@ skipping corrupted item %@", item, localError);
2166 } else {
2167 if (status == errSecInteractionNotAllowed)
2168 *stop = true;
2169 // Propagate the first other error upwards (causing the restore to fail).
2170 secerror("restore %@ failed %@", item, localError);
2171 ok = false;
2172 if (error && !*error) {
2173 *error = localError;
2174 localError = NULL;
2175 }
2176 }
2177 CFReleaseSafe(localError);
2178 }
2179 }
2180 });
2181 ok &= SOSDataSourceRelease(ds, error);
2182 CFReleaseNull(mdelete);
2183 CFReleaseNull(madd);
2184 CFReleaseNull(mnow);
2185 });
2186 CFRelease(mbackup);
2187 }
2188
2189 ok &= ks_close_keybag(bag_handle, error);
2190
2191 return ok;
2192 }
2193
2194
2195 CF_RETURNS_RETAINED CFDictionaryRef
2196 _SecServerBackupSyncable(CFDictionaryRef backup, CFDataRef keybag, CFDataRef password, CFErrorRef *error) {
2197 require_action_quiet(isData(keybag), errOut, SecError(errSecParam, error, CFSTR("keybag %@ not a data"), keybag));
2198 require_action_quiet(!backup || isDictionary(backup), errOut, SecError(errSecParam, error, CFSTR("backup %@ not a dictionary"), backup));
2199 require_action_quiet(!password || isData(password), errOut, SecError(errSecParam, error, CFSTR("password %@ not a data"), password));
2200
2201 return _SecServerCopyTruthInTheCloud(keybag, password, backup, error);
2202
2203 errOut:
2204 return NULL;
2205 }
2206
2207 bool
2208 _SecServerRestoreSyncable(CFDictionaryRef backup, CFDataRef keybag, CFDataRef password, CFErrorRef *error) {
2209 bool ok;
2210 require_action_quiet(isData(keybag), errOut, ok = SecError(errSecParam, error, CFSTR("keybag %@ not a data"), keybag));
2211 require_action_quiet(isDictionary(backup), errOut, ok = SecError(errSecParam, error, CFSTR("backup %@ not a dictionary"), backup));
2212 if (password) {
2213
2214 require_action_quiet(isData(password), errOut, ok = SecError(errSecParam, error, CFSTR("password not a data")));
2215 }
2216
2217 ok = _SecServerRestoreTruthInTheCloud(keybag, password, backup, error);
2218
2219 errOut:
2220 return ok;
2221 }
2222
2223 bool _SecServerRollKeysGlue(bool force, CFErrorRef *error) {
2224 return _SecServerRollKeys(force, NULL, error);
2225 }
2226
2227
2228 bool _SecServerRollKeys(bool force, SecurityClient *client, CFErrorRef *error) {
2229 #if USE_KEYSTORE
2230 uint32_t keystore_generation_status = 0;
2231 if (aks_generation(KEYBAG_DEVICE, generation_noop, &keystore_generation_status))
2232 return false;
2233 uint32_t current_generation = keystore_generation_status & generation_current;
2234
2235 return kc_with_dbt(true, error, ^(SecDbConnectionRef dbt) {
2236 bool up_to_date = s3dl_dbt_keys_current(dbt, current_generation, NULL);
2237
2238 if (force && !up_to_date) {
2239 up_to_date = s3dl_dbt_update_keys(dbt, client, error);
2240 if (up_to_date) {
2241 secerror("Completed roll keys.");
2242 up_to_date = s3dl_dbt_keys_current(dbt, current_generation, NULL);
2243 }
2244 if (!up_to_date)
2245 secerror("Failed to roll keys.");
2246 }
2247 return up_to_date;
2248 });
2249 #else
2250 return true;
2251 #endif
2252 }
2253
2254 #if TARGET_OS_IOS
2255
2256 /*
2257 * Sync bubble migration code
2258 */
2259
2260 struct SyncBubbleRule {
2261 CFStringRef attribute;
2262 CFTypeRef value;
2263 };
2264
2265 static bool
2266 TransmogrifyItemsToSyncBubble(SecurityClient *client, uid_t uid,
2267 bool onlyDelete,
2268 bool copyToo,
2269 const SecDbClass *qclass,
2270 struct SyncBubbleRule *items, CFIndex nItems,
2271 CFErrorRef *error)
2272 {
2273 CFMutableDictionaryRef updateAttributes = NULL;
2274 CFDataRef syncBubbleView = NULL;
2275 CFDataRef activeUserView = NULL;
2276 bool res = false;
2277 Query *q = NULL;
2278 CFIndex n;
2279
2280 syncBubbleView = SecMUSRCreateSyncBubbleUserUUID(uid);
2281 require(syncBubbleView, fail);
2282
2283 activeUserView = SecMUSRCreateActiveUserUUID(uid);
2284 require(activeUserView, fail);
2285
2286
2287 if ((onlyDelete && !copyToo) || !onlyDelete) {
2288
2289 /*
2290 * Clean out items first
2291 */
2292
2293 secnotice("syncbubble", "cleaning out old items");
2294
2295 q = query_create(qclass, NULL, NULL, error);
2296 require(q, fail);
2297
2298 q->q_limit = kSecMatchUnlimited;
2299 q->q_keybag = device_keybag_handle;
2300
2301 for (n = 0; n < nItems; n++) {
2302 query_add_attribute(items[n].attribute, items[n].value, q);
2303 }
2304 q->q_musrView = CFRetain(syncBubbleView);
2305 require(q->q_musrView, fail);
2306
2307 kc_with_dbt(false, error, ^(SecDbConnectionRef dbt) {
2308 return kc_transaction(dbt, error, ^{
2309 return s3dl_query_delete(dbt, q, NULL, error);
2310 });
2311 });
2312
2313 query_destroy(q, NULL);
2314 q = NULL;
2315 }
2316
2317
2318 if (onlyDelete || !copyToo) {
2319 secnotice("syncbubble", "skip migration of items");
2320 } else {
2321 /*
2322 * Copy over items from EMCS to sync bubble
2323 */
2324
2325 secnotice("syncbubble", "migrating sync bubble items");
2326
2327 q = query_create(qclass, NULL, NULL, error);
2328 require(q, fail);
2329
2330 q->q_return_type = kSecReturnDataMask | kSecReturnAttributesMask;
2331 q->q_limit = kSecMatchUnlimited;
2332 q->q_keybag = device_keybag_handle; /* XXX change to session key bag when it exists */
2333
2334 for (n = 0; n < nItems; n++) {
2335 query_add_or_attribute(items[n].attribute, items[n].value, q);
2336 }
2337 query_add_or_attribute(CFSTR("musr"), activeUserView, q);
2338 q->q_musrView = CFRetain(activeUserView);
2339
2340 updateAttributes = CFDictionaryCreateMutableForCFTypes(NULL);
2341 require(updateAttributes, fail);
2342
2343 CFDictionarySetValue(updateAttributes, CFSTR("musr"), syncBubbleView); /* XXX should use kSecAttrMultiUser */
2344
2345
2346 kc_with_dbt(true, error, ^(SecDbConnectionRef dbt) {
2347 return kc_transaction(dbt, error, ^{
2348 CFErrorRef error2 = NULL;
2349
2350 SecDbItemSelect(q, dbt, &error2, NULL, ^bool(const SecDbAttr *attr) {
2351 return CFDictionaryGetValue(q->q_item, attr->name);
2352 }, NULL, NULL, ^(SecDbItemRef item, bool *stop) {
2353 CFErrorRef error3 = NULL;
2354 secinfo("syncbubble", "migrating item");
2355
2356 SecDbItemRef new_item = SecDbItemCopyWithUpdates(item, updateAttributes, NULL);
2357 if (new_item == NULL)
2358 return;
2359
2360 SecDbItemClearRowId(new_item, NULL);
2361
2362 if (!SecDbItemSetKeybag(new_item, device_keybag_handle, NULL)) {
2363 CFRelease(new_item);
2364 return;
2365 }
2366
2367 if (!SecDbItemInsert(new_item, dbt, &error3)) {
2368 secnotice("syncbubble", "migration failed with %@ for item %@", error3, new_item);
2369 }
2370 CFRelease(new_item);
2371 CFReleaseNull(error3);
2372 });
2373 CFReleaseNull(error2);
2374
2375 return (bool)true;
2376 });
2377 });
2378 }
2379 res = true;
2380
2381 fail:
2382 CFReleaseNull(syncBubbleView);
2383 CFReleaseNull(activeUserView);
2384 CFReleaseNull(updateAttributes);
2385 if (q)
2386 query_destroy(q, NULL);
2387
2388 return res;
2389 }
2390
2391 static struct SyncBubbleRule PCSItems[] = {
2392 {
2393 .attribute = CFSTR("agrp"),
2394 .value = CFSTR("com.apple.ProtectedCloudStorage"),
2395 }
2396 };
2397 static struct SyncBubbleRule NSURLSesssiond[] = {
2398 {
2399 .attribute = CFSTR("agrp"),
2400 .value = CFSTR("com.apple.nsurlsessiond"),
2401 }
2402 };
2403 static struct SyncBubbleRule AccountsdItems[] = {
2404 {
2405 .attribute = CFSTR("svce"),
2406 .value = CFSTR("com.apple.account.AppleAccount.token"),
2407 },
2408 {
2409 .attribute = CFSTR("svce"),
2410 .value = CFSTR("com.apple.account.AppleAccount.password"),
2411 },
2412 {
2413 .attribute = CFSTR("svce"),
2414 .value = CFSTR("com.apple.account.AppleAccount.rpassword"),
2415 },
2416 {
2417 .attribute = CFSTR("svce"),
2418 .value = CFSTR("com.apple.account.idms.token"),
2419 },
2420 {
2421 .attribute = CFSTR("svce"),
2422 .value = CFSTR("com.apple.account.idms.continuation-key"),
2423 },
2424 {
2425 .attribute = CFSTR("svce"),
2426 .value = CFSTR("com.apple.account.CloudKit.token"),
2427 },
2428 };
2429
2430 static struct SyncBubbleRule MobileMailItems[] = {
2431 {
2432 .attribute = CFSTR("svce"),
2433 .value = CFSTR("com.apple.account.IMAP.password"),
2434 },
2435 {
2436 .attribute = CFSTR("svce"),
2437 .value = CFSTR("com.apple.account.SMTP.password"),
2438 },
2439 {
2440 .attribute = CFSTR("svce"),
2441 .value = CFSTR("com.apple.account.Exchange.password"),
2442 },
2443 {
2444 .attribute = CFSTR("svce"),
2445 .value = CFSTR("com.apple.account.Hotmail.password"),
2446 },
2447 {
2448 .attribute = CFSTR("svce"),
2449 .value = CFSTR("com.apple.account.Google.password"),
2450 },
2451 {
2452 .attribute = CFSTR("svce"),
2453 .value = CFSTR("com.apple.account.Google.oauth-token"),
2454 },
2455 {
2456 .attribute = CFSTR("svce"),
2457 .value = CFSTR("com.apple.account.Google.oath-refresh-token"),
2458 },
2459 {
2460 .attribute = CFSTR("svce"),
2461 .value = CFSTR("com.apple.account.Yahoo.password"),
2462 },
2463 {
2464 .attribute = CFSTR("svce"),
2465 .value = CFSTR("com.apple.account.Yahoo.oauth-token"),
2466 },
2467 {
2468 .attribute = CFSTR("svce"),
2469 .value = CFSTR("com.apple.account.Yahoo.oauth-token-nosync"),
2470 },
2471 {
2472 .attribute = CFSTR("svce"),
2473 .value = CFSTR("com.apple.account.Yahoo.oath-refresh-token"),
2474 },
2475 {
2476 .attribute = CFSTR("svce"),
2477 .value = CFSTR("com.apple.account.IMAPNotes.password"),
2478 },
2479 {
2480 .attribute = CFSTR("svce"),
2481 .value = CFSTR("com.apple.account.IMAPMail.password"),
2482 },
2483 {
2484 .attribute = CFSTR("svce"),
2485 .value = CFSTR("com.apple.account.126.password"),
2486 },
2487 {
2488 .attribute = CFSTR("svce"),
2489 .value = CFSTR("com.apple.account.163.password"),
2490 },
2491 {
2492 .attribute = CFSTR("svce"),
2493 .value = CFSTR("com.apple.account.aol.password"),
2494 },
2495 };
2496
2497 static bool
2498 ArrayContains(CFArrayRef array, CFStringRef service)
2499 {
2500 return CFArrayContainsValue(array, CFRangeMake(0, CFArrayGetCount(array)), service);
2501 }
2502
2503 bool
2504 _SecServerTransmogrifyToSyncBubble(CFArrayRef services, uid_t uid, SecurityClient *client, CFErrorRef *error)
2505 {
2506 bool copyCloudAuthToken = false;
2507 bool copyMobileMail = false;
2508 bool res = true;
2509 bool copyPCS = false;
2510 bool onlyDelete = false;
2511 bool copyNSURLSesssion = false;
2512
2513 if (!client->inMultiUser)
2514 return false;
2515
2516 secnotice("syncbubble", "migration for uid %d uid for services %@", (int)uid, services);
2517
2518 #if TARGET_OS_SIMULATOR
2519 // no delete in sim
2520 #elif TARGET_OS_IOS
2521 if (uid != (uid_t)client->activeUser)
2522 onlyDelete = true;
2523 #else
2524 #error "no sync bubble on other platforms"
2525 #endif
2526
2527 /*
2528 * First select that services to copy/delete
2529 */
2530
2531 if (ArrayContains(services, CFSTR("com.apple.bird.usermanager.sync"))
2532 || ArrayContains(services, CFSTR("com.apple.cloudphotod.sync"))
2533 || ArrayContains(services, CFSTR("com.apple.cloudphotod.syncstakeholder"))
2534 || ArrayContains(services, CFSTR("com.apple.cloudd.usermanager.sync")))
2535 {
2536 copyCloudAuthToken = true;
2537 copyPCS = true;
2538 }
2539
2540 if (ArrayContains(services, CFSTR("com.apple.nsurlsessiond.usermanager.sync")))
2541 {
2542 copyCloudAuthToken = true;
2543 copyNSURLSesssion = true;
2544 }
2545
2546 if (ArrayContains(services, CFSTR("com.apple.syncdefaultsd.usermanager.sync"))) {
2547 copyCloudAuthToken = true;
2548 }
2549 if (ArrayContains(services, CFSTR("com.apple.mailq.sync")) || ArrayContains(services, CFSTR("com.apple.mailq.sync.xpc"))) {
2550 copyCloudAuthToken = true;
2551 copyMobileMail = true;
2552 copyPCS = true;
2553 }
2554
2555 /*
2556 * The actually copy/delete the items selected
2557 */
2558
2559 res = TransmogrifyItemsToSyncBubble(client, uid, onlyDelete, copyPCS, &inet_class, PCSItems, sizeof(PCSItems)/sizeof(PCSItems[0]), error);
2560 require(res, fail);
2561 res = TransmogrifyItemsToSyncBubble(client, uid, onlyDelete, copyPCS, &genp_class, PCSItems, sizeof(PCSItems)/sizeof(PCSItems[0]), error);
2562 require(res, fail);
2563
2564 /* mail */
2565 res = TransmogrifyItemsToSyncBubble(client, uid, onlyDelete, copyMobileMail, &genp_class, MobileMailItems, sizeof(MobileMailItems)/sizeof(MobileMailItems[0]), error);
2566 require(res, fail);
2567
2568 /* accountsd */
2569 res = TransmogrifyItemsToSyncBubble(client, uid, onlyDelete, copyCloudAuthToken, &genp_class, AccountsdItems, sizeof(AccountsdItems)/sizeof(AccountsdItems[0]), error);
2570 require(res, fail);
2571
2572 /* nsurlsessiond */
2573 res = TransmogrifyItemsToSyncBubble(client, uid, onlyDelete, copyNSURLSesssion, &inet_class, NSURLSesssiond, sizeof(NSURLSesssiond)/sizeof(NSURLSesssiond[0]), error);
2574 require(res, fail);
2575
2576 fail:
2577 return res;
2578 }
2579
2580 /*
2581 * Migrate from user keychain to system keychain when switching to edu mode
2582 */
2583
2584 bool
2585 _SecServerTransmogrifyToSystemKeychain(SecurityClient *client, CFErrorRef *error)
2586 {
2587 __block bool ok = true;
2588
2589 /*
2590 * we are not in multi user yet, about to switch, otherwise we would
2591 * check that for client->inMultiuser here
2592 */
2593
2594 kc_with_dbt(true, error, ^(SecDbConnectionRef dbt) {
2595 return kc_transaction(dbt, error, ^{
2596 CFDataRef systemUUID = SecMUSRGetSystemKeychainUUID();
2597
2598 const SecDbSchema *newSchema = kc_schemas[0];
2599 SecDbClass const *const *kcClass;
2600
2601 for (kcClass = newSchema->classes; *kcClass != NULL; kcClass++) {
2602 CFErrorRef localError = NULL;
2603 Query *q = NULL;
2604
2605 if (*kcClass == &tversion_class || *kcClass == &identity_class)
2606 continue;
2607
2608 q = query_create(*kcClass, SecMUSRGetSingleUserKeychainUUID(), NULL, error);
2609 if (q == NULL)
2610 continue;
2611
2612 ok &= SecDbItemSelect(q, dbt, error, ^bool(const SecDbAttr *attr) {
2613 return (attr->flags & kSecDbInFlag) != 0;
2614 }, ^bool(const SecDbAttr *attr) {
2615 // No filtering please.
2616 return false;
2617 }, ^bool(CFMutableStringRef sql, bool *needWhere) {
2618 SecDbAppendWhereOrAnd(sql, needWhere);
2619 CFStringAppendFormat(sql, NULL, CFSTR("musr = ?"));
2620 return true;
2621 }, ^bool(sqlite3_stmt *stmt, int col) {
2622 return SecDbBindObject(stmt, col++, SecMUSRGetSingleUserKeychainUUID(), error);
2623 }, ^(SecDbItemRef item, bool *stop) {
2624 CFErrorRef localError = NULL;
2625
2626 if (!SecDbItemSetValueWithName(item, kSecAttrMultiUser, systemUUID, &localError)) {
2627 secerror("item: %@ update musr to system failed: %@", item, localError);
2628 ok = false;
2629 goto out;
2630 }
2631
2632 if (!SecDbItemDoUpdate(item, item, dbt, &localError, ^bool (const SecDbAttr *attr) {
2633 return attr->kind == kSecDbRowIdAttr;
2634 })) {
2635 secerror("item: %@ insert during UPDATE: %@", item, localError);
2636 ok = false;
2637 goto out;
2638 }
2639
2640 out:
2641 SecErrorPropagate(localError, error);
2642 CFReleaseSafe(localError);
2643 });
2644
2645 if (q)
2646 query_destroy(q, &localError);
2647
2648 }
2649 return (bool)true;
2650 });
2651 });
2652
2653 return ok;
2654 }
2655
2656 /*
2657 * Delete account from local usage
2658 */
2659
2660 bool
2661 _SecServerDeleteMUSERViews(SecurityClient *client, uid_t uid, CFErrorRef *error)
2662 {
2663 return kc_with_dbt(true, error, ^(SecDbConnectionRef dbt) {
2664 CFDataRef musrView = NULL, syncBubbleView = NULL;
2665 bool ok = false;
2666
2667 syncBubbleView = SecMUSRCreateSyncBubbleUserUUID(uid);
2668 require(syncBubbleView, fail);
2669
2670 musrView = SecMUSRCreateActiveUserUUID(uid);
2671 require(musrView, fail);
2672
2673 require(ok = SecServerDeleteAllForUser(dbt, syncBubbleView, false, error), fail);
2674 require(ok = SecServerDeleteAllForUser(dbt, musrView, false, error), fail);
2675
2676 fail:
2677 CFReleaseNull(syncBubbleView);
2678 CFReleaseNull(musrView);
2679 return ok;
2680 });
2681 }
2682
2683
2684 #endif /* TARGET_OS_IOS */
2685
2686 bool
2687 _SecServerGetKeyStats(const SecDbClass *qclass,
2688 struct _SecServerKeyStats *stats)
2689 {
2690 __block CFErrorRef error = NULL;
2691 bool res = false;
2692
2693 Query *q = query_create(qclass, NULL, NULL, &error);
2694 require(q, fail);
2695
2696 q->q_return_type = kSecReturnDataMask | kSecReturnAttributesMask;
2697 q->q_limit = kSecMatchUnlimited;
2698 q->q_keybag = KEYBAG_DEVICE;
2699 query_add_or_attribute(kSecAttrAccessible, kSecAttrAccessibleWhenUnlocked, q);
2700 query_add_or_attribute(kSecAttrAccessible, kSecAttrAccessibleAfterFirstUnlock, q);
2701 query_add_or_attribute(kSecAttrAccessible, kSecAttrAccessibleAlways, q);
2702 query_add_or_attribute(kSecAttrAccessible, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, q);
2703 query_add_or_attribute(kSecAttrAccessible, kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, q);
2704 query_add_or_attribute(kSecAttrAccessible, kSecAttrAccessibleAlwaysThisDeviceOnly, q);
2705 query_add_attribute(kSecAttrTombstone, kCFBooleanFalse, q);
2706
2707 kc_with_dbt(false, &error, ^(SecDbConnectionRef dbconn) {
2708 CFErrorRef error2 = NULL;
2709 __block CFIndex totalSize = 0;
2710 stats->maxDataSize = 0;
2711
2712 SecDbItemSelect(q, dbconn, &error2, NULL, ^bool(const SecDbAttr *attr) {
2713 return CFDictionaryContainsKey(q->q_item, attr->name);
2714 }, NULL, NULL, ^(SecDbItemRef item, bool *stop) {
2715 CFErrorRef error3 = NULL;
2716 CFDataRef data = SecDbItemGetValue(item, &v6v_Data, &error3);
2717 if (isData(data)) {
2718 CFIndex size = CFDataGetLength(data);
2719 if (size > stats->maxDataSize)
2720 stats->maxDataSize = size;
2721 totalSize += size;
2722 stats->items++;
2723 }
2724 CFReleaseNull(error3);
2725 });
2726 CFReleaseNull(error2);
2727 if (stats->items)
2728 stats->averageSize = totalSize / stats->items;
2729
2730 return (bool)true;
2731 });
2732
2733
2734 res = true;
2735
2736 fail:
2737 CFReleaseNull(error);
2738 if (q)
2739 query_destroy(q, NULL);
2740 return res;
2741 }