2 * Copyright (c) 2006-2015 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
25 * SecItemServer.c - CoreFoundation-based constants and functions for
26 access to Security items (certificates, keys, identities, and
30 #include <securityd/SecItemServer.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>
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>
49 /* defines from <Security/SecEntitlements.h> */
50 #define kSecEntitlementAssociatedDomains CFSTR("com.apple.developer.associated-domains")
51 #define kSecEntitlementPrivateAssociatedDomains CFSTR("com.apple.private.associated-domains")
54 #if TARGET_OS_EMBEDDED
55 #include <AggregateDictionary/ADClient.h>
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"
66 #if TARGET_OS_IPHONE && !TARGET_OS_NANO
68 #include <SharedWebCredentials/SharedWebCredentials.h>
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
);
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 )
83 /* Changed the name of the keychain changed notification, for testing */
84 static const char *g_keychain_changed_notification
= kSecServerKeychainChangedNotification
;
86 void SecItemServerSetKeychainChangedNotification(const char *notification_name
)
88 g_keychain_changed_notification
= notification_name
;
91 void SecKeychainChanged(bool syncWithPeers
) {
92 uint32_t result
= notify_post(g_keychain_changed_notification
);
94 SOSCCSyncWithAllPeers();
95 if (result
== NOTIFY_STATUS_OK
)
96 secnotice("item", "Sent %s%s", syncWithPeers
? "SyncWithAllPeers and " : "", g_keychain_changed_notification
);
98 secerror("%snotify_post %s returned: %" PRIu32
, syncWithPeers
? "Sent SyncWithAllPeers, " : "", g_keychain_changed_notification
, result
);
101 /* Return the current database version in *version. */
102 static bool SecKeychainDbGetVersion(SecDbConnectionRef dbt
, int *version
, CFErrorRef
*error
)
104 __block
bool ok
= false;
105 SecDbQueryRef query
= NULL
;
106 __block CFNumberRef versionNumber
= NULL
;
107 __block CFErrorRef localError
= NULL
;
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.
113 }, ^bool(const SecDbAttr
*attr
) {
116 }, NULL
, NULL
, ^(SecDbItemRef item
, bool *stop
) {
117 versionNumber
= copyNumber(SecDbItemGetValue(item
, tversion_class
.attrs
[0], &localError
));
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
));
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
);
138 query_destroy(query
, NULL
);
139 CFReleaseSafe(versionNumber
);
140 return ok
|| CFErrorPropagate(localError
, error
);
144 isClassD(SecDbItemRef item
)
146 CFTypeRef accessible
= SecDbItemGetCachedValueWithName(item
, kSecAttrAccessible
);
148 if (CFEqualSafe(accessible
, kSecAttrAccessibleAlways
) || CFEqualSafe(accessible
, kSecAttrAccessibleAlwaysThisDeviceOnly
))
153 #if TARGET_OS_EMBEDDED
156 measureDuration(struct timeval
*start
)
161 gettimeofday(&stop
, NULL
);
163 duration
= (stop
.tv_sec
-start
->tv_sec
) * 1000;
164 duration
+= (stop
.tv_usec
/ 1000) - (start
->tv_usec
/ 1000);
166 return SecBucket2Significant(duration
);
170 measureUpgradePhase1(struct timeval
*start
, bool success
, int64_t itemsMigrated
)
172 int64_t duration
= measureDuration(start
);
175 ADClientSetValueForScalarKey(CFSTR("com.apple.keychain.phase1.migrated-items-success"), itemsMigrated
);
176 ADClientSetValueForScalarKey(CFSTR("com.apple.keychain.phase1.migrated-time-success"), duration
);
178 ADClientSetValueForScalarKey(CFSTR("com.apple.keychain.phase1.migrated-items-fail"), itemsMigrated
);
179 ADClientSetValueForScalarKey(CFSTR("com.apple.keychain.phase1.migrated-time-fail"), duration
);
184 measureUpgradePhase2(struct timeval
*start
, int64_t itemsMigrated
)
186 int64_t duration
= measureDuration(start
);
188 ADClientSetValueForScalarKey(CFSTR("com.apple.keychain.phase2.migrated-items"), itemsMigrated
);
189 ADClientSetValueForScalarKey(CFSTR("com.apple.keychain.phase2.migrated-time"), duration
);
191 #endif /* TARGET_OS_EMBEDDED */
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
)
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
;
206 gettimeofday(&start
, NULL
);
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
);
217 CFStringAppendFormat(sql
, NULL
, CFSTR("DROP TABLE %@;"), (*oldClass
)->name
);
220 require_quiet(ok
&= SecDbExec(dbt
, sql
, error
), out
);
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
);
230 require_quiet(ok
&= SecDbExec(dbt
, sql
, error
), out
);
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
))
241 secnotice("upgr", "Upgrading table %@", (*oldClass
)->name
);
243 // Prepare query to iterate through all items in cur_class.
245 query_destroy(query
, NULL
);
246 require_quiet(query
= query_create(*oldClass
, SecMUSRGetAllViews(), NULL
, error
), out
);
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.
254 }, NULL
, NULL
, ^(SecDbItemRef item
, bool *stop
) {
255 CFErrorRef localError
= NULL
;
257 #if TARGET_OS_EMBEDDED
260 // Switch item to the new class.
261 item
->class = *newClass
;
263 if (isClassD(item
)) {
265 ok
&= SecDbItemEnsureDecrypted(item
, &localError
);
266 require_quiet(ok
, out
);
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
);
273 // Leave item encrypted, do not ever try to decrypt it since it will fail.
274 item
->_edataState
= kSecDbItemAlwaysEncrypted
;
277 // Insert new item into the new table.
278 if (!SecDbItemInsert(item
, dbt
, &localError
)) {
279 secerror("item: %@ insert during upgrade: %@", item
, localError
);
285 OSStatus status
= SecErrorGetOSStatus(localError
);
288 // continue to upgrade and don't propagate errors for insert failures
289 // that are typical of a single item failure
291 case errSecDuplicateItem
:
294 case errSecInteractionNotAllowed
:
295 case errSecAuthNeeded
:
299 ok
&= CFErrorPropagate(CFRetainSafe(localError
), error
);
302 CFReleaseSafe(localError
);
307 require_quiet(ok
, out
);
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
);
318 require_quiet(ok
&= SecDbExec(dbt
, sql
, error
), out
);
321 #if TARGET_OS_EMBEDDED
322 measureUpgradePhase1(&start
, ok
, SecBucket2Significant(itemsMigrated
));
326 query_destroy(query
, NULL
);
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
;
340 gettimeofday(&start
, NULL
);
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
351 const SecDbAttr
*pdmn
= SecDbClassAttrWithKind(*class, kSecDbAccessAttr
, error
);
356 // Prepare query to go through all non-DK|DKU items
358 query_destroy(query
, NULL
);
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.
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
);
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
;
375 #if TARGET_OS_EMBEDDED
380 if (SecDbItemEnsureDecrypted(item
, &localError
)) {
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
);
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
);
393 CFIndex status
= CFErrorGetCode(localError
);
397 // Items producing errSecDecode are silently dropped - they are not decodable and lost forever.
398 (void)SecDbItemDelete(item
, dbt
, false, error
);
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.
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).
416 // Other errors should abort the migration completely.
417 ok
= CFErrorPropagate(CFRetainSafe(localError
), error
);
423 CFReleaseSafe(localError
);
424 *stop
= *stop
|| !ok
;
430 #if TARGET_OS_EMBEDDED
431 measureUpgradePhase2(&start
, SecBucket2Significant(itemsMigrated
));
436 query_destroy(query
, NULL
);
440 static bool SecKeychainDbUpgradeFromVersion(SecDbConnectionRef dbt
, int version
, bool *inProgress
, CFErrorRef
*error
) {
441 __block
bool didPhase2
= false;
442 __block
bool ok
= true;
444 // The schema we want to have is the first in the list of schemas.
445 const SecDbSchema
*newSchema
= kc_schemas
[0];
447 // If DB schema is the one we want, we are done.
448 require_quiet(newSchema
->version
!= version
, out
);
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
));
459 ok
&= SecDbTransaction(dbt
, kSecDbExclusiveTransactionType
, error
, ^(bool *commit
) {
460 CFStringRef sql
= NULL
;
461 bool didPhase1
= false;
463 // Get version again once we start a transaction, someone else might change the migration state.
465 require_quiet(ok
= SecKeychainDbGetVersion(dbt
, &version
, error
), out
);
466 require_quiet(newSchema
->version
!= version
, out
);
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
));
471 int oldVersion
= (version
>> 16) & 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
));
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
;
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
;
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
));
498 secnotice("upgr", "Upgrading from version %d to %d", oldVersion
, newSchema
->version
);
499 require(ok
= UpgradeSchemaPhase1(dbt
, oldSchema
, error
), out
);
505 CFErrorRef phase2Error
= NULL
;
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
);
516 SecErrorPropagate(phase2Error
, error
);
519 CFReleaseNull(phase2Error
);
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
);
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
);
544 if (ok
&& didPhase2
) {
545 #if TARGET_OS_EMBEDDED
546 ADClientAddValueForScalarKey(CFSTR("com.apple.keychain.migration-success"), 1);
551 if (!ok
|| (error
&& *error
)) {
553 secwarning("upgrade: error has been set but status is true");
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);
566 /* AUDIT[securityd](done):
567 accessGroup (ok) is a caller provided, non NULL CFTypeRef.
569 Return true iff accessGroup is allowable according to accessGroups.
571 static bool accessGroupsAllows(CFArrayRef accessGroups
,
572 CFStringRef accessGroup
) {
573 /* NULL accessGroups is wildcard. */
576 /* Make sure we have a string. */
577 if (!isString(accessGroup
))
580 /* Having the special accessGroup "*" allows access to all accessGroups. */
581 CFRange range
= { 0, CFArrayGetCount(accessGroups
) };
583 (CFArrayContainsValue(accessGroups
, range
, accessGroup
) ||
584 CFArrayContainsValue(accessGroups
, range
, CFSTR("*"))))
590 bool itemInAccessGroup(CFDictionaryRef item
, CFArrayRef accessGroups
) {
591 return accessGroupsAllows(accessGroups
,
592 CFDictionaryGetValue(item
, kSecAttrAccessGroup
));
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()
602 CFDictionaryRef keychain
= SecServerCopyKeychainPlist(dbt
, client
,
603 src_keybag
, dest_keybag
, kSecBackupableItemFilter
,
606 data_out
= CFPropertyListCreateData(kCFAllocatorDefault
, keychain
,
607 kCFPropertyListBinaryFormat_v1_0
,
615 static bool SecServerImportBackupableKeychain(SecDbConnectionRef dbt
,
616 SecurityClient
*client
,
617 keybag_handle_t src_keybag
,
618 keybag_handle_t dest_keybag
,
622 return kc_transaction(dbt
, error
, ^{
624 CFDictionaryRef keychain
;
625 keychain
= CFPropertyListCreateWithData(kCFAllocatorDefault
, data
,
626 kCFPropertyListImmutable
, NULL
,
629 if (isDictionary(keychain
)) {
630 ok
= SecServerImportKeychainInPlist(dbt
,
635 kSecBackupableItemFilter
,
638 ok
= SecError(errSecParam
, error
, CFSTR("import: keychain is not a dictionary"));
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
);
660 static bool SecServerKeychainRestore(SecDbConnectionRef dbt
,
661 SecurityClient
*client
,
667 keybag_handle_t backup_keybag
;
668 if (!ks_open_keybag(keybag
, password
, &backup_keybag
, error
))
671 /* Import from backup keybag to system keybag. */
672 bool ok
= SecServerImportBackupableKeychain(dbt
, client
, backup_keybag
, KEYBAG_DEVICE
,
674 ok
&= ks_close_keybag(backup_keybag
, error
);
680 // MARK - External SPI support code.
682 CFStringRef
__SecKeychainCopyPath(void) {
683 CFStringRef kcRelPath
= NULL
;
685 kcRelPath
= CFSTR("keychain-2.db");
687 kcRelPath
= CFSTR("keychain-2-debug.db");
690 CFStringRef kcPath
= NULL
;
691 CFURLRef kcURL
= SecCopyURLForFileInKeychainDirectory(kcRelPath
);
693 kcPath
= CFURLCopyFileSystemPath(kcURL
, kCFURLPOSIXPathStyle
);
700 // MARK: kc_dbhandle init and reset
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.
708 ok
= SecKeychainDbGetVersion(dbconn
, &version
, error
);
710 ok
= ok
&& SecKeychainDbUpgradeFromVersion(dbconn
, version
, callMeAgainForNextConnection
, error
);
712 secerror("Upgrade %sfailed: %@", didCreate
? "from v0 " : "", error
? *error
: NULL
);
718 static SecDbRef _kc_dbhandle
= NULL
;
720 static void kc_dbhandle_init(void) {
721 SecDbRef oldHandle
= _kc_dbhandle
;
723 CFStringRef dbPath
= __SecKeychainCopyPath();
725 _kc_dbhandle
= SecKeychainDbCreate(dbPath
);
728 secerror("no keychain path available");
731 secerror("replaced %@ with %@", oldHandle
, _kc_dbhandle
);
732 CFRelease(oldHandle
);
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
);
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
);
748 static dispatch_once_t _kc_dbhandle_once
;
750 static SecDbRef
kc_dbhandle(void) {
751 dispatch_once(&_kc_dbhandle_once
, ^{
752 setup_sqlite3_defaults_settings();
758 /* For whitebox testing only */
759 void SecKeychainDbReset(dispatch_block_t inbetween
)
761 CFStringRef dbPath
= __SecKeychainCopyPath();
765 CFReleaseNull(_kc_dbhandle
);
770 _kc_dbhandle
= SecKeychainDbCreate(dbPath
);
774 static SecDbConnectionRef
kc_aquire_dbt(bool writeAndRead
, CFErrorRef
*error
) {
775 SecDbRef db
= kc_dbhandle();
777 SecError(errSecDataNotAvailable
, error
, CFSTR("failed to get a db handle"));
780 return SecDbConnectionAquire(db
, !writeAndRead
, error
);
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
))
788 // Make sure we initialize our engines before writing to the keychain
790 SecItemDataSourceFactoryGetDefault();
793 SecDbConnectionRef dbt
= kc_aquire_dbt(writeAndRead
, error
);
796 SecDbConnectionRelease(dbt
);
802 items_matching_issuer_parent(SecDbConnectionRef dbt
, CFArrayRef accessGroups
, CFDataRef musrView
,
803 CFDataRef issuer
, CFArrayRef issuers
, int recurse
)
806 CFArrayRef results
= NULL
;
810 if (CFArrayContainsValue(issuers
, CFRangeMake(0, CFArrayGetCount(issuers
)), issuer
))
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
);
821 CFErrorRef localError
= NULL
;
822 q
= query_create_with_limit(query
, musrView
, kSecMatchUnlimited
, &localError
);
825 s3dl_copy_matching(dbt
, q
, (CFTypeRef
*)&results
, accessGroups
, &localError
);
826 query_destroy(q
, &localError
);
829 secerror("items matching issuer parent: %@", localError
);
830 CFReleaseNull(localError
);
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
))
841 found
= items_matching_issuer_parent(dbt
, accessGroups
, musrView
, cert_issuer
, issuers
, recurse
);
843 CFReleaseSafe(results
);
848 bool match_item(SecDbConnectionRef dbt
, Query
*q
, CFArrayRef accessGroups
, CFDictionaryRef item
)
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*/))
856 /* Add future match checks here. */
861 /****************************************************************************
862 **************** Beginning of Externally Callable Interface ****************
863 ****************************************************************************/
865 void (*SecTaskDiagnoseEntitlements
)(CFArrayRef accessGroups
) = NULL
;
867 /* AUDIT[securityd](done):
868 query (ok) is a caller provided dictionary, only its cf type has been checked.
871 SecItemServerCopyMatching(CFDictionaryRef query
, CFTypeRef
*result
,
872 SecurityClient
*client
, CFErrorRef
*error
)
874 CFArrayRef accessGroups
= client
->accessGroups
;
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"));
884 if (CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), CFSTR("*"))) {
885 /* Having the special accessGroup "*" allows access to all accessGroups. */
890 Query
*q
= query_create_with_limit(query
, client
->musr
, 1, error
);
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
);
898 CFRetainSafe(accessGroups
);
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();
912 q
->q_system_keychain
= false;
916 query_set_caller_access_groups(q
, accessGroups
);
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
);
938 CFReleaseSafe(accessGroups
);
939 if (!query_destroy(q
, error
))
947 _SecItemCopyMatching(CFDictionaryRef query
, SecurityClient
*client
, CFTypeRef
*result
, CFErrorRef
*error
) {
948 return SecItemServerCopyMatching(query
, result
, client
, error
);
953 SecItemSynchronizable(CFDictionaryRef query
)
956 CFTypeRef value
= CFDictionaryGetValue(query
, kSecAttrSynchronizable
);
957 if (isBoolean(value
))
958 return CFBooleanGetValue(value
);
959 else if (isNumber(value
)) {
961 (void)CFNumberGetValue(value
, kCFNumberSInt32Type
, &number
);
970 /* AUDIT[securityd](done):
971 attributes (ok) is a caller provided dictionary, only its cf type has
975 _SecItemAdd(CFDictionaryRef attributes
, SecurityClient
*client
, CFTypeRef
*result
, CFErrorRef
*error
)
977 CFArrayRef accessGroups
= client
->accessGroups
;
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"));
988 Query
*q
= query_create_with_limit(attributes
, client
->musr
, 0, error
);
990 /* Access group sanity checking. */
991 CFStringRef agrp
= (CFStringRef
)CFDictionaryGetValue(attributes
,
992 kSecAttrAccessGroup
);
994 /* Having the special accessGroup "*" allows access to all accessGroups. */
995 if (CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), CFSTR("*")))
999 /* The user specified an explicit access group, validate it. */
1000 if (!accessGroupsAllows(accessGroups
, agrp
))
1001 ok
= SecError(errSecNoAccessForItem
, error
, CFSTR("NoAccessForItem"));
1003 agrp
= (CFStringRef
)CFArrayGetValueAtIndex(client
->accessGroups
, 0);
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
);
1009 #if TARGET_OS_IPHONE
1010 if (q
->q_system_keychain
&& client
->inMultiUser
) {
1011 CFReleaseNull(q
->q_musrView
);
1012 q
->q_musrView
= SecMUSRCopySystemKeychainUUID();
1014 q
->q_system_keychain
= false;
1016 query_add_attribute_with_desc(&v8musr
, q
->q_musrView
, q
);
1020 query_ensure_access_control(q
, agrp
);
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"));
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
);
1041 ok
= query_notify_and_destroy(q
, ok
, error
);
1048 /* AUDIT[securityd](done):
1049 query (ok) and attributesToUpdate (ok) are a caller provided dictionaries,
1050 only their cf types have been checked.
1053 _SecItemUpdate(CFDictionaryRef query
, CFDictionaryRef attributesToUpdate
,
1054 SecurityClient
*client
, CFErrorRef
*error
)
1056 CFArrayRef accessGroups
= client
->accessGroups
;
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"));
1066 if (CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), CFSTR("*"))) {
1067 /* Having the special accessGroup "*" allows access to all accessGroups. */
1068 accessGroups
= NULL
;
1072 Query
*q
= query_create_with_limit(query
, client
->musr
, kSecMatchUnlimited
, error
);
1077 #if TARGET_OS_IPHONE
1078 if (q
->q_system_keychain
&& client
->inMultiUser
) {
1079 CFReleaseNull(q
->q_musrView
);
1080 q
->q_musrView
= SecMUSRCopySystemKeychainUUID();
1082 q
->q_system_keychain
= false;
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"));
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"));
1108 /* Access group sanity checking. */
1109 CFStringRef agrp
= (CFStringRef
)CFDictionaryGetValue(attributesToUpdate
,
1110 kSecAttrAccessGroup
);
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
);
1121 ok
= kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
1122 return s3dl_query_update(dbt
, q
, attributesToUpdate
, accessGroups
, error
);
1126 ok
= query_notify_and_destroy(q
, ok
, error
);
1132 /* AUDIT[securityd](done):
1133 query (ok) is a caller provided dictionary, only its cf type has been checked.
1136 _SecItemDelete(CFDictionaryRef query
, SecurityClient
*client
, CFErrorRef
*error
)
1138 CFArrayRef accessGroups
= client
->accessGroups
;
1141 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
))) {
1142 if (SecTaskDiagnoseEntitlements
)
1143 SecTaskDiagnoseEntitlements(accessGroups
);
1144 return SecError(errSecMissingEntitlement
, error
,
1145 CFSTR("client has neither application-identifier nor keychain-access-groups entitlements"));
1148 if (CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), CFSTR("*"))) {
1149 /* Having the special accessGroup "*" allows access to all accessGroups. */
1150 accessGroups
= NULL
;
1153 Query
*q
= query_create_with_limit(query
, client
->musr
, kSecMatchUnlimited
, error
);
1156 #if TARGET_OS_IPHONE
1157 if (q
->q_system_keychain
&& client
->inMultiUser
) {
1158 CFReleaseNull(q
->q_musrView
);
1159 q
->q_musrView
= SecMUSRCopySystemKeychainUUID();
1161 q
->q_system_keychain
= false;
1165 query_set_caller_access_groups(q
, accessGroups
);
1166 /* Sanity check the query. */
1167 if (q
->q_system_keychain
&& !client
->allowSystemKeychain
) {
1168 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for system keychain"));
1169 } else if (q
->q_sync_bubble
&& !client
->allowSyncBubbleKeychain
) {
1170 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for syncbubble keychain"));
1171 } else if (q
->q_limit
!= kSecMatchUnlimited
) {
1172 ok
= SecError(errSecMatchLimitUnsupported
, error
, CFSTR("match limit not supported by delete"));
1173 } else if (query_match_count(q
) != 0) {
1174 ok
= SecError(errSecItemMatchUnsupported
, error
, CFSTR("match not supported by delete"));
1175 } else if (q
->q_ref
) {
1176 ok
= SecError(errSecValueRefUnsupported
, error
, CFSTR("value ref not supported by delete"));
1177 } else if (q
->q_row_id
&& query_attr_count(q
)) {
1178 ok
= SecError(errSecItemIllegalQuery
, error
, CFSTR("rowid and other attributes are mutually exclusive"));
1180 ok
= kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
1181 return s3dl_query_delete(dbt
, q
, accessGroups
, error
);
1184 ok
= query_notify_and_destroy(q
, ok
, error
);
1192 /* AUDIT[securityd](done):
1193 No caller provided inputs.
1196 SecItemServerDeleteAll(CFErrorRef
*error
) {
1197 return kc_with_dbt(true, error
, ^bool (SecDbConnectionRef dbt
) {
1198 return (kc_transaction(dbt
, error
, ^bool {
1199 return (SecDbExec(dbt
, CFSTR("DELETE from genp;"), error
) &&
1200 SecDbExec(dbt
, CFSTR("DELETE from inet;"), error
) &&
1201 SecDbExec(dbt
, CFSTR("DELETE from cert;"), error
) &&
1202 SecDbExec(dbt
, CFSTR("DELETE from keys;"), error
));
1203 }) && SecDbExec(dbt
, CFSTR("VACUUM;"), error
));
1208 _SecItemDeleteAll(CFErrorRef
*error
) {
1209 return SecItemServerDeleteAll(error
);
1214 // MARK: Shared web credentials
1217 #define SEC_CONST_DECL(k,v) const CFStringRef k = CFSTR(v);
1219 SEC_CONST_DECL (kSecSafariAccessGroup
, "com.apple.cfnetwork");
1220 SEC_CONST_DECL (kSecSafariDefaultComment
, "default");
1221 SEC_CONST_DECL (kSecSafariPasswordsNotSaved
, "Passwords not saved");
1222 SEC_CONST_DECL (kSecSharedCredentialUrlScheme
, "https://");
1223 SEC_CONST_DECL (kSecSharedWebCredentialsService
, "webcredentials");
1225 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1226 static dispatch_once_t sSecSWCInitializeOnce
= 0;
1227 static void * sSecSWCLibrary
= NULL
;
1228 static SWCCheckService_f sSWCCheckService_f
= NULL
;
1229 static SWCSetServiceFlags_f sSWCSetServiceFlags_f
= NULL
;
1231 static OSStatus
_SecSWCEnsuredInitialized(void);
1233 static OSStatus
_SecSWCEnsuredInitialized(void)
1235 __block OSStatus status
= errSecNotAvailable
;
1237 dispatch_once(&sSecSWCInitializeOnce
, ^{
1238 sSecSWCLibrary
= dlopen("/System/Library/PrivateFrameworks/SharedWebCredentials.framework/SharedWebCredentials", RTLD_LAZY
| RTLD_LOCAL
);
1239 assert(sSecSWCLibrary
);
1240 if (sSecSWCLibrary
) {
1241 sSWCCheckService_f
= (SWCCheckService_f
)(uintptr_t) dlsym(sSecSWCLibrary
, "SWCCheckService");
1242 sSWCSetServiceFlags_f
= (SWCSetServiceFlags_f
)(uintptr_t) dlsym(sSecSWCLibrary
, "SWCSetServiceFlags");
1246 if (sSWCCheckService_f
&& sSWCSetServiceFlags_f
) {
1253 #if !TARGET_IPHONE_SIMULATOR
1255 _SecAppDomainApprovalStatus(CFStringRef appID
, CFStringRef fqdn
, CFErrorRef
*error
)
1257 __block SWCFlags flags
= kSWCFlags_None
;
1259 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1260 OSStatus status
= _SecSWCEnsuredInitialized();
1262 SecError(status
, error
, CFSTR("SWC initialize failed"));
1265 CFRetainSafe(appID
);
1267 dispatch_semaphore_t semaphore
= dispatch_semaphore_create(0);
1268 dispatch_retain(semaphore
);
1269 if (0 == sSWCCheckService_f(kSecSharedWebCredentialsService
, appID
, fqdn
,
1270 ^void (OSStatus inStatus
, SWCFlags inFlags
, CFDictionaryRef inDetails
) {
1271 if (!inStatus
) { flags
= inFlags
; }
1272 CFReleaseSafe(appID
);
1273 CFReleaseSafe(fqdn
);
1274 dispatch_semaphore_signal(semaphore
);
1275 dispatch_release(semaphore
);
1276 //secerror("SWCCheckService: inStatus=%d, flags=%0X", inStatus, flags);
1279 // wait for the block to complete, as we need its answer
1280 dispatch_semaphore_wait(semaphore
, DISPATCH_TIME_FOREVER
);
1282 else // didn't queue the block
1284 CFReleaseSafe(appID
);
1285 CFReleaseSafe(fqdn
);
1286 dispatch_release(semaphore
);
1288 dispatch_release(semaphore
);
1290 flags
|= (kSWCFlag_SiteApproved
);
1293 if (!error
) { return flags
; }
1296 // check website approval status
1297 if (!(flags
& kSWCFlag_SiteApproved
)) {
1298 if (flags
& kSWCFlag_Pending
) {
1299 SecError(errSecAuthFailed
, error
, CFSTR("Approval is pending for \"%@\", try later"), fqdn
);
1301 SecError(errSecAuthFailed
, error
, CFSTR("\"%@\" failed to approve \"%@\""), fqdn
, appID
);
1306 // check user approval status
1307 if (flags
& kSWCFlag_UserDenied
) {
1308 SecError(errSecAuthFailed
, error
, CFSTR("User denied access to \"%@\" by \"%@\""), fqdn
, appID
);
1314 #if !TARGET_IPHONE_SIMULATOR
1316 _SecEntitlementContainsDomainForService(CFArrayRef domains
, CFStringRef domain
, CFStringRef service
)
1318 bool result
= false;
1319 CFIndex idx
, count
= (domains
) ? CFArrayGetCount(domains
) : (CFIndex
) 0;
1320 if (!count
|| !domain
|| !service
) {
1323 for (idx
=0; idx
< count
; idx
++) {
1324 CFStringRef str
= (CFStringRef
) CFArrayGetValueAtIndex(domains
, idx
);
1325 if (str
&& CFStringHasPrefix(str
, kSecSharedWebCredentialsService
)) {
1326 CFIndex prefix_len
= CFStringGetLength(kSecSharedWebCredentialsService
)+1;
1327 CFIndex substr_len
= CFStringGetLength(str
) - prefix_len
;
1328 CFRange range
= { prefix_len
, substr_len
};
1329 CFStringRef substr
= CFStringCreateWithSubstring(kCFAllocatorDefault
, str
, range
);
1330 if (substr
&& CFEqual(substr
, domain
)) {
1333 CFReleaseSafe(substr
);
1344 _SecAddNegativeWebCredential(CFStringRef fqdn
, CFStringRef appID
, bool forSafari
)
1346 bool result
= false;
1347 if (!fqdn
) { return result
; }
1349 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1350 OSStatus status
= _SecSWCEnsuredInitialized();
1351 if (status
) { return false; }
1353 // update our database
1354 CFRetainSafe(appID
);
1356 if (0 == sSWCSetServiceFlags_f(kSecSharedWebCredentialsService
,
1357 appID
, fqdn
, kSWCFlag_ExternalMask
, kSWCFlag_UserDenied
,
1358 ^void(OSStatus inStatus
, SWCFlags inNewFlags
){
1359 CFReleaseSafe(appID
);
1360 CFReleaseSafe(fqdn
);
1365 else // didn't queue the block
1367 CFReleaseSafe(appID
);
1368 CFReleaseSafe(fqdn
);
1371 if (!forSafari
) { return result
; }
1373 // below this point: create a negative Safari web credential item
1375 CFMutableDictionaryRef attrs
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1376 if (!attrs
) { return result
; }
1378 CFErrorRef error
= NULL
;
1379 CFStringRef accessGroup
= CFSTR("*");
1380 SecurityClient client
= {
1382 .accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&accessGroup
, 1, &kCFTypeArrayCallBacks
),
1383 .allowSystemKeychain
= false,
1384 .allowSyncBubbleKeychain
= false,
1385 .isNetworkExtension
= false,
1388 CFDictionaryAddValue(attrs
, kSecClass
, kSecClassInternetPassword
);
1389 CFDictionaryAddValue(attrs
, kSecAttrAccessGroup
, kSecSafariAccessGroup
);
1390 CFDictionaryAddValue(attrs
, kSecAttrAuthenticationType
, kSecAttrAuthenticationTypeHTMLForm
);
1391 CFDictionaryAddValue(attrs
, kSecAttrProtocol
, kSecAttrProtocolHTTPS
);
1392 CFDictionaryAddValue(attrs
, kSecAttrServer
, fqdn
);
1393 CFDictionaryAddValue(attrs
, kSecAttrSynchronizable
, kCFBooleanTrue
);
1395 (void)_SecItemDelete(attrs
, &client
, &error
);
1396 CFReleaseNull(error
);
1398 CFDictionaryAddValue(attrs
, kSecAttrAccount
, kSecSafariPasswordsNotSaved
);
1399 CFDictionaryAddValue(attrs
, kSecAttrComment
, kSecSafariDefaultComment
);
1401 CFStringRef label
= CFStringCreateWithFormat(kCFAllocatorDefault
,
1402 NULL
, CFSTR("%@ (%@)"), fqdn
, kSecSafariPasswordsNotSaved
);
1404 CFDictionaryAddValue(attrs
, kSecAttrLabel
, label
);
1405 CFReleaseSafe(label
);
1409 CFDataRef data
= CFDataCreate(kCFAllocatorDefault
, &space
, 1);
1411 CFDictionarySetValue(attrs
, kSecValueData
, data
);
1412 CFReleaseSafe(data
);
1415 CFTypeRef addResult
= NULL
;
1416 result
= _SecItemAdd(attrs
, &client
, &addResult
, &error
);
1418 CFReleaseSafe(addResult
);
1419 CFReleaseSafe(error
);
1420 CFReleaseSafe(attrs
);
1421 CFReleaseSafe(client
.accessGroups
);
1426 /* Specialized version of SecItemAdd for shared web credentials */
1428 _SecAddSharedWebCredential(CFDictionaryRef attributes
,
1429 const audit_token_t
*clientAuditToken
,
1433 CFErrorRef
*error
) {
1435 SecurityClient client
= {};
1437 CFStringRef fqdn
= CFRetainSafe(CFDictionaryGetValue(attributes
, kSecAttrServer
));
1438 CFStringRef account
= CFRetainSafe(CFDictionaryGetValue(attributes
, kSecAttrAccount
));
1439 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1440 CFStringRef password
= CFRetainSafe(CFDictionaryGetValue(attributes
, kSecSharedPassword
));
1442 CFStringRef password
= CFRetainSafe(CFDictionaryGetValue(attributes
, CFSTR("spwd")));
1444 CFStringRef accessGroup
= CFSTR("*");
1445 CFMutableDictionaryRef query
= NULL
, attrs
= NULL
;
1447 bool ok
= false, update
= false;
1448 //bool approved = false;
1450 // check autofill enabled status
1451 if (!swca_autofill_enabled(clientAuditToken
)) {
1452 SecError(errSecBadReq
, error
, CFSTR("Autofill is not enabled in Safari settings"));
1456 // parse fqdn with CFURL here, since it could be specified as domain:port
1458 CFStringRef urlStr
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%@%@"), kSecSharedCredentialUrlScheme
, fqdn
);
1460 CFURLRef url
= CFURLCreateWithString(kCFAllocatorDefault
, urlStr
, nil
);
1462 CFStringRef hostname
= CFURLCopyHostName(url
);
1464 CFReleaseSafe(fqdn
);
1466 port
= CFURLGetPortNumber(url
);
1470 CFReleaseSafe(urlStr
);
1475 SecError(errSecParam
, error
, CFSTR("No account provided"));
1479 SecError(errSecParam
, error
, CFSTR("No domain provided"));
1483 #if TARGET_IPHONE_SIMULATOR
1484 secerror("app/site association entitlements not checked in Simulator");
1486 OSStatus status
= errSecMissingEntitlement
;
1487 // validate that fqdn is part of caller's shared credential domains entitlement
1489 SecError(status
, error
, CFSTR("Missing application-identifier entitlement"));
1492 if (_SecEntitlementContainsDomainForService(domains
, fqdn
, kSecSharedWebCredentialsService
)) {
1493 status
= errSecSuccess
;
1495 if (errSecSuccess
!= status
) {
1496 CFStringRef msg
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
,
1497 CFSTR("%@ not found in %@ entitlement"), fqdn
, kSecEntitlementAssociatedDomains
);
1499 msg
= CFRetain(CFSTR("Requested domain not found in entitlement"));
1501 SecError(status
, error
, CFSTR("%@"), msg
);
1507 #if TARGET_IPHONE_SIMULATOR
1508 secerror("Ignoring app/site approval state in the Simulator.");
1510 // get approval status for this app/domain pair
1511 SWCFlags flags
= _SecAppDomainApprovalStatus(appID
, fqdn
, error
);
1512 //approved = ((flags & kSWCFlag_SiteApproved) && (flags & kSWCFlag_UserApproved));
1513 if (!(flags
& kSWCFlag_SiteApproved
)) {
1518 // give ourselves access to see matching items for kSecSafariAccessGroup
1520 client
.accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&accessGroup
, 1, &kCFTypeArrayCallBacks
);
1521 client
.allowSystemKeychain
= false;
1522 client
.allowSyncBubbleKeychain
= false;
1523 client
.isNetworkExtension
= false;
1526 // create lookup query
1527 query
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1529 SecError(errSecAllocate
, error
, CFSTR("Unable to create query dictionary"));
1532 CFDictionaryAddValue(query
, kSecClass
, kSecClassInternetPassword
);
1533 CFDictionaryAddValue(query
, kSecAttrAccessGroup
, kSecSafariAccessGroup
);
1534 CFDictionaryAddValue(query
, kSecAttrAuthenticationType
, kSecAttrAuthenticationTypeHTMLForm
);
1535 CFDictionaryAddValue(query
, kSecAttrServer
, fqdn
);
1536 CFDictionaryAddValue(query
, kSecAttrSynchronizable
, kCFBooleanTrue
);
1538 // check for presence of Safari's negative entry ('passwords not saved')
1539 CFDictionarySetValue(query
, kSecAttrAccount
, kSecSafariPasswordsNotSaved
);
1540 ok
= _SecItemCopyMatching(query
, &client
, result
, error
);
1541 if(result
) CFReleaseNull(*result
);
1542 CFReleaseNull(*error
);
1544 SecError(errSecDuplicateItem
, error
, CFSTR("Item already exists for this server"));
1548 // now use the provided account (and optional port number, if one was present)
1549 CFDictionarySetValue(query
, kSecAttrAccount
, account
);
1550 if (port
< -1 || port
> 0) {
1551 SInt16 portValueShort
= (port
& 0xFFFF);
1552 CFNumberRef portNumber
= CFNumberCreate(NULL
, kCFNumberSInt16Type
, &portValueShort
);
1553 CFDictionaryAddValue(query
, kSecAttrPort
, portNumber
);
1554 CFReleaseSafe(portNumber
);
1557 // look up existing password
1558 if (_SecItemCopyMatching(query
, &client
, result
, error
)) {
1559 // found it, so this becomes either an "update password" or "delete password" operation
1560 if(result
) CFReleaseNull(*result
);
1561 CFReleaseNull(*error
);
1562 update
= (password
!= NULL
);
1564 attrs
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1565 CFDataRef credential
= CFStringCreateExternalRepresentation(kCFAllocatorDefault
, password
, kCFStringEncodingUTF8
, 0);
1566 CFDictionaryAddValue(attrs
, kSecValueData
, credential
);
1567 CFReleaseSafe(credential
);
1568 CFDictionaryAddValue(attrs
, kSecAttrComment
, kSecSafariDefaultComment
);
1570 // confirm the update
1571 // (per rdar://16676310 we always prompt, even if there was prior user approval)
1572 ok
= /*approved ||*/ swca_confirm_operation(swca_update_request_id
, clientAuditToken
, query
, error
,
1573 ^void (CFStringRef fqdn
) { _SecAddNegativeWebCredential(fqdn
, appID
, false); });
1575 ok
= _SecItemUpdate(query
, attrs
, &client
, error
);
1579 // confirm the delete
1580 // (per rdar://16676288 we always prompt, even if there was prior user approval)
1581 ok
= /*approved ||*/ swca_confirm_operation(swca_delete_request_id
, clientAuditToken
, query
, error
,
1582 ^void (CFStringRef fqdn
) { _SecAddNegativeWebCredential(fqdn
, appID
, false); });
1584 ok
= _SecItemDelete(query
, &client
, error
);
1588 CFReleaseNull(*error
);
1592 if(result
) CFReleaseNull(*result
);
1593 CFReleaseNull(*error
);
1595 // password does not exist, so prepare to add it
1597 // a NULL password value removes the existing credential. Since we didn't find it, this is a no-op.
1602 CFStringRef label
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%@ (%@)"), fqdn
, account
);
1604 CFDictionaryAddValue(query
, kSecAttrLabel
, label
);
1605 CFReleaseSafe(label
);
1607 // NOTE: we always expect to use HTTPS for web forms.
1608 CFDictionaryAddValue(query
, kSecAttrProtocol
, kSecAttrProtocolHTTPS
);
1610 CFDataRef credential
= CFStringCreateExternalRepresentation(kCFAllocatorDefault
, password
, kCFStringEncodingUTF8
, 0);
1611 CFDictionarySetValue(query
, kSecValueData
, credential
);
1612 CFReleaseSafe(credential
);
1613 CFDictionarySetValue(query
, kSecAttrComment
, kSecSafariDefaultComment
);
1615 CFReleaseSafe(client
.accessGroups
);
1616 client
.accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&kSecSafariAccessGroup
, 1, &kCFTypeArrayCallBacks
);
1618 // mark the item as created by this function
1619 const int32_t creator_value
= 'swca';
1620 CFNumberRef creator
= CFNumberCreate(kCFAllocatorDefault
, kCFNumberSInt32Type
, &creator_value
);
1622 CFDictionarySetValue(query
, kSecAttrCreator
, creator
);
1623 CFReleaseSafe(creator
);
1628 // (per rdar://16680019, we won't prompt here in the normal case)
1629 ok
= /*approved ||*/ swca_confirm_operation(swca_add_request_id
, clientAuditToken
, query
, error
,
1630 ^void (CFStringRef fqdn
) { _SecAddNegativeWebCredential(fqdn
, appID
, false); });
1634 ok
= _SecItemAdd(query
, &client
, result
, error
);
1638 #if 0 /* debugging */
1640 const char *op_str
= (password
) ? ((update
) ? "updated" : "added") : "deleted";
1641 const char *result_str
= (ok
) ? "true" : "false";
1642 secerror("result=%s, %s item %@, error=%@", result_str
, op_str
, *result
, *error
);
1647 CFReleaseSafe(attrs
);
1648 CFReleaseSafe(query
);
1649 CFReleaseSafe(client
.accessGroups
);
1650 CFReleaseSafe(fqdn
);
1651 CFReleaseSafe(account
);
1652 CFReleaseSafe(password
);
1656 /* Specialized version of SecItemCopyMatching for shared web credentials */
1658 _SecCopySharedWebCredential(CFDictionaryRef query
,
1659 const audit_token_t
*clientAuditToken
,
1663 CFErrorRef
*error
) {
1665 CFMutableArrayRef credentials
= NULL
;
1666 CFMutableArrayRef foundItems
= NULL
;
1667 CFMutableArrayRef fqdns
= NULL
;
1668 CFStringRef fqdn
= NULL
;
1669 CFStringRef account
= NULL
;
1674 require_quiet(result
, cleanup
);
1675 credentials
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
1676 foundItems
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
1677 fqdns
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
1679 // give ourselves access to see matching items for kSecSafariAccessGroup
1680 CFStringRef accessGroup
= CFSTR("*");
1681 SecurityClient client
= {
1683 .accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&accessGroup
, 1, &kCFTypeArrayCallBacks
),
1684 .allowSystemKeychain
= false,
1685 .allowSyncBubbleKeychain
= false,
1686 .isNetworkExtension
= false,
1689 // On input, the query dictionary contains optional fqdn and account entries.
1690 fqdn
= CFDictionaryGetValue(query
, kSecAttrServer
);
1691 account
= CFDictionaryGetValue(query
, kSecAttrAccount
);
1693 // Check autofill enabled status
1694 if (!swca_autofill_enabled(clientAuditToken
)) {
1695 SecError(errSecBadReq
, error
, CFSTR("Autofill is not enabled in Safari settings"));
1699 // Check fqdn; if NULL, add domains from caller's entitlement.
1701 CFArrayAppendValue(fqdns
, fqdn
);
1704 CFIndex idx
, count
= CFArrayGetCount(domains
);
1705 for (idx
=0; idx
< count
; idx
++) {
1706 CFStringRef str
= (CFStringRef
) CFArrayGetValueAtIndex(domains
, idx
);
1707 // Parse the entry for our service label prefix
1708 if (str
&& CFStringHasPrefix(str
, kSecSharedWebCredentialsService
)) {
1709 CFIndex prefix_len
= CFStringGetLength(kSecSharedWebCredentialsService
)+1;
1710 CFIndex substr_len
= CFStringGetLength(str
) - prefix_len
;
1711 CFRange range
= { prefix_len
, substr_len
};
1712 fqdn
= CFStringCreateWithSubstring(kCFAllocatorDefault
, str
, range
);
1714 CFArrayAppendValue(fqdns
, fqdn
);
1720 count
= CFArrayGetCount(fqdns
);
1722 SecError(errSecParam
, error
, CFSTR("No domain provided"));
1726 // Aggregate search results for each domain
1727 for (idx
= 0; idx
< count
; idx
++) {
1728 CFMutableArrayRef items
= NULL
;
1729 CFMutableDictionaryRef attrs
= NULL
;
1730 fqdn
= (CFStringRef
) CFArrayGetValueAtIndex(fqdns
, idx
);
1734 // Parse the fqdn for a possible port specifier.
1736 CFStringRef urlStr
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%@%@"), kSecSharedCredentialUrlScheme
, fqdn
);
1738 CFURLRef url
= CFURLCreateWithString(kCFAllocatorDefault
, urlStr
, nil
);
1740 CFStringRef hostname
= CFURLCopyHostName(url
);
1742 CFReleaseSafe(fqdn
);
1744 port
= CFURLGetPortNumber(url
);
1748 CFReleaseSafe(urlStr
);
1752 #if TARGET_IPHONE_SIMULATOR
1753 secerror("app/site association entitlements not checked in Simulator");
1755 OSStatus status
= errSecMissingEntitlement
;
1757 SecError(status
, error
, CFSTR("Missing application-identifier entitlement"));
1758 CFReleaseSafe(fqdn
);
1761 // validate that fqdn is part of caller's entitlement
1762 if (_SecEntitlementContainsDomainForService(domains
, fqdn
, kSecSharedWebCredentialsService
)) {
1763 status
= errSecSuccess
;
1765 if (errSecSuccess
!= status
) {
1766 CFStringRef msg
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
,
1767 CFSTR("%@ not found in %@ entitlement"), fqdn
, kSecEntitlementAssociatedDomains
);
1769 msg
= CFRetain(CFSTR("Requested domain not found in entitlement"));
1771 SecError(status
, error
, CFSTR("%@"), msg
);
1773 CFReleaseSafe(fqdn
);
1778 attrs
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1780 SecError(errSecAllocate
, error
, CFSTR("Unable to create query dictionary"));
1781 CFReleaseSafe(fqdn
);
1784 CFDictionaryAddValue(attrs
, kSecClass
, kSecClassInternetPassword
);
1785 CFDictionaryAddValue(attrs
, kSecAttrAccessGroup
, kSecSafariAccessGroup
);
1786 CFDictionaryAddValue(attrs
, kSecAttrAuthenticationType
, kSecAttrAuthenticationTypeHTMLForm
);
1787 CFDictionaryAddValue(attrs
, kSecAttrServer
, fqdn
);
1789 CFDictionaryAddValue(attrs
, kSecAttrAccount
, account
);
1791 if (port
< -1 || port
> 0) {
1792 SInt16 portValueShort
= (port
& 0xFFFF);
1793 CFNumberRef portNumber
= CFNumberCreate(NULL
, kCFNumberSInt16Type
, &portValueShort
);
1794 CFDictionaryAddValue(attrs
, kSecAttrPort
, portNumber
);
1795 CFReleaseSafe(portNumber
);
1797 CFDictionaryAddValue(attrs
, kSecAttrSynchronizable
, kCFBooleanTrue
);
1798 CFDictionaryAddValue(attrs
, kSecMatchLimit
, kSecMatchLimitAll
);
1799 CFDictionaryAddValue(attrs
, kSecReturnAttributes
, kCFBooleanTrue
);
1800 CFDictionaryAddValue(attrs
, kSecReturnData
, kCFBooleanTrue
);
1802 ok
= _SecItemCopyMatching(attrs
, &client
, (CFTypeRef
*)&items
, error
);
1804 // ignore interim error since we have multiple domains to search
1805 CFReleaseNull(*error
);
1807 if (ok
&& items
&& CFGetTypeID(items
) == CFArrayGetTypeID()) {
1808 #if TARGET_IPHONE_SIMULATOR
1809 secerror("Ignoring app/site approval state in the Simulator.");
1810 bool approved
= true;
1812 // get approval status for this app/domain pair
1813 SWCFlags flags
= _SecAppDomainApprovalStatus(appID
, fqdn
, error
);
1815 // ignore interim error since we have multiple domains to check
1816 CFReleaseNull(*error
);
1818 bool approved
= (flags
& kSWCFlag_SiteApproved
);
1821 CFArrayAppendArray(foundItems
, items
, CFRangeMake(0, CFArrayGetCount(items
)));
1824 CFReleaseSafe(items
);
1825 CFReleaseSafe(attrs
);
1826 CFReleaseSafe(fqdn
);
1829 // If matching credentials are found, the credentials provided to the completionHandler
1830 // will be a CFArrayRef containing CFDictionaryRef entries. Each dictionary entry will
1831 // contain the following pairs (see Security/SecItem.h):
1832 // key: kSecAttrServer value: CFStringRef (the website)
1833 // key: kSecAttrAccount value: CFStringRef (the account)
1834 // key: kSecSharedPassword value: CFStringRef (the password)
1836 // key: kSecAttrPort value: CFNumberRef (the port number, if non-standard for https)
1838 count
= CFArrayGetCount(foundItems
);
1839 for (idx
= 0; idx
< count
; idx
++) {
1840 CFDictionaryRef dict
= (CFDictionaryRef
) CFArrayGetValueAtIndex(foundItems
, idx
);
1841 CFMutableDictionaryRef newdict
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1842 if (newdict
&& dict
&& CFGetTypeID(dict
) == CFDictionaryGetTypeID()) {
1843 CFStringRef srvr
= CFDictionaryGetValue(dict
, kSecAttrServer
);
1844 CFStringRef acct
= CFDictionaryGetValue(dict
, kSecAttrAccount
);
1845 CFNumberRef pnum
= CFDictionaryGetValue(dict
, kSecAttrPort
);
1846 CFStringRef icmt
= CFDictionaryGetValue(dict
, kSecAttrComment
);
1847 CFDataRef data
= CFDictionaryGetValue(dict
, kSecValueData
);
1849 CFDictionaryAddValue(newdict
, kSecAttrServer
, srvr
);
1852 CFDictionaryAddValue(newdict
, kSecAttrAccount
, acct
);
1856 if (CFNumberGetValue(pnum
, kCFNumberSInt16Type
, &pval
) &&
1857 (pval
< -1 || pval
> 0)) {
1858 CFDictionaryAddValue(newdict
, kSecAttrPort
, pnum
);
1862 CFStringRef password
= CFStringCreateFromExternalRepresentation(kCFAllocatorDefault
, data
, kCFStringEncodingUTF8
);
1864 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1865 CFDictionaryAddValue(newdict
, kSecSharedPassword
, password
);
1867 CFDictionaryAddValue(newdict
, CFSTR("spwd"), password
);
1869 CFReleaseSafe(password
);
1872 if (icmt
&& CFEqual(icmt
, kSecSafariDefaultComment
)) {
1873 CFArrayInsertValueAtIndex(credentials
, 0, newdict
);
1875 CFArrayAppendValue(credentials
, newdict
);
1878 CFReleaseSafe(newdict
);
1885 // create a new array of dictionaries (without the actual password) for picker UI
1886 count
= CFArrayGetCount(credentials
);
1887 CFMutableArrayRef items
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
1888 for (idx
= 0; idx
< count
; idx
++) {
1889 CFDictionaryRef dict
= (CFDictionaryRef
) CFArrayGetValueAtIndex(credentials
, idx
);
1890 CFMutableDictionaryRef newdict
= CFDictionaryCreateMutableCopy(kCFAllocatorDefault
, 0, dict
);
1891 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1892 CFDictionaryRemoveValue(newdict
, kSecSharedPassword
);
1894 CFDictionaryRemoveValue(newdict
, CFSTR("spwd"));
1896 CFArrayAppendValue(items
, newdict
);
1897 CFReleaseSafe(newdict
);
1900 // prompt user to select one of the dictionary items
1901 CFDictionaryRef selected
= swca_copy_selected_dictionary(swca_select_request_id
,
1902 clientAuditToken
, items
, error
);
1904 // find the matching item in our credentials array
1905 CFStringRef srvr
= CFDictionaryGetValue(selected
, kSecAttrServer
);
1906 CFStringRef acct
= CFDictionaryGetValue(selected
, kSecAttrAccount
);
1907 CFNumberRef pnum
= CFDictionaryGetValue(selected
, kSecAttrPort
);
1908 for (idx
= 0; idx
< count
; idx
++) {
1909 CFDictionaryRef dict
= (CFDictionaryRef
) CFArrayGetValueAtIndex(credentials
, idx
);
1910 CFStringRef srvr1
= CFDictionaryGetValue(dict
, kSecAttrServer
);
1911 CFStringRef acct1
= CFDictionaryGetValue(dict
, kSecAttrAccount
);
1912 CFNumberRef pnum1
= CFDictionaryGetValue(dict
, kSecAttrPort
);
1914 if (!srvr
|| !srvr1
|| !CFEqual(srvr
, srvr1
)) continue;
1915 if (!acct
|| !acct1
|| !CFEqual(acct
, acct1
)) continue;
1916 if ((pnum
&& pnum1
) && !CFEqual(pnum
, pnum1
)) continue;
1919 CFReleaseSafe(selected
);
1926 CFReleaseSafe(items
);
1927 CFArrayRemoveAllValues(credentials
);
1928 if (selected
&& ok
) {
1929 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1930 fqdn
= CFDictionaryGetValue(selected
, kSecAttrServer
);
1932 CFArrayAppendValue(credentials
, selected
);
1936 // confirm the access
1937 ok
= swca_confirm_operation(swca_copy_request_id
, clientAuditToken
, query
, error
,
1938 ^void (CFStringRef fqdn
) { _SecAddNegativeWebCredential(fqdn
, appID
, false); });
1941 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1942 // register confirmation with database
1943 OSStatus status
= _SecSWCEnsuredInitialized();
1945 SecError(status
, error
, CFSTR("SWC initialize failed"));
1947 CFReleaseSafe(selected
);
1950 CFRetainSafe(appID
);
1952 if (0 != sSWCSetServiceFlags_f(kSecSharedWebCredentialsService
,
1953 appID
, fqdn
, kSWCFlag_ExternalMask
, kSWCFlag_UserApproved
,
1954 ^void(OSStatus inStatus
, SWCFlags inNewFlags
){
1955 CFReleaseSafe(appID
);
1956 CFReleaseSafe(fqdn
);
1959 // we didn't queue the block
1960 CFReleaseSafe(appID
);
1961 CFReleaseSafe(fqdn
);
1965 CFReleaseSafe(selected
);
1967 else if (NULL
== *error
) {
1968 // found no items, and we haven't already filled in the error
1969 SecError(errSecItemNotFound
, error
, CFSTR("no matching items found"));
1974 CFArrayRemoveAllValues(credentials
);
1976 CFReleaseSafe(foundItems
);
1977 *result
= credentials
;
1978 CFReleaseSafe(client
.accessGroups
);
1979 CFReleaseSafe(fqdns
);
1980 #if 0 /* debugging */
1981 secerror("result=%s, copied items %@, error=%@", (ok
) ? "true" : "false", *result
, *error
);
1987 // MARK: Keychain backup
1989 CF_RETURNS_RETAINED CFDataRef
1990 _SecServerKeychainCreateBackup(SecurityClient
*client
, CFDataRef keybag
, CFDataRef passcode
, CFErrorRef
*error
) {
1992 SecDbConnectionRef dbt
= SecDbConnectionAquire(kc_dbhandle(), false, error
);
1997 if (keybag
== NULL
&& passcode
== NULL
) {
1999 backup
= SecServerExportBackupableKeychain(dbt
, client
, KEYBAG_DEVICE
, backup_keybag_handle
, error
);
2000 #else /* !USE_KEYSTORE */
2002 SecError(errSecParam
, error
, CFSTR("Why are you doing this?"));
2004 #endif /* USE_KEYSTORE */
2006 backup
= SecServerKeychainCreateBackup(dbt
, client
, keybag
, passcode
, error
);
2009 SecDbConnectionRelease(dbt
);
2015 _SecServerKeychainRestore(CFDataRef backup
, SecurityClient
*client
, CFDataRef keybag
, CFDataRef passcode
, CFErrorRef
*error
) {
2016 if (backup
== NULL
|| keybag
== NULL
)
2017 return SecError(errSecParam
, error
, CFSTR("backup or keybag missing"));
2019 __block
bool ok
= true;
2020 ok
&= SecDbPerformWrite(kc_dbhandle(), error
, ^(SecDbConnectionRef dbconn
) {
2021 ok
= SecServerKeychainRestore(dbconn
, client
, backup
, keybag
, passcode
, error
);
2025 SecKeychainChanged(true);
2033 // MARK: SecItemDataSource
2035 // Make sure to call this before any writes to the keychain, so that we fire
2036 // up the engines to monitor manifest changes.
2037 SOSDataSourceFactoryRef
SecItemDataSourceFactoryGetDefault(void) {
2038 return SecItemDataSourceFactoryGetShared(kc_dbhandle());
2041 /* AUDIT[securityd]:
2042 args_in (ok) is a caller provided, CFDictionaryRef.
2045 CF_RETURNS_RETAINED CFArrayRef
2046 _SecServerKeychainSyncUpdateMessage(CFDictionaryRef updates
, CFErrorRef
*error
) {
2047 // This never fails, trust us!
2048 return SOSCCHandleUpdateMessage(updates
);
2052 // Truthiness in the cloud backup/restore support.
2055 static CFDictionaryRef
2056 _SecServerCopyTruthInTheCloud(CFDataRef keybag
, CFDataRef password
,
2057 CFDictionaryRef backup
, CFErrorRef
*error
)
2059 SOSManifestRef mold
= NULL
, mnow
= NULL
, mdelete
= NULL
, madd
= NULL
;
2060 __block CFMutableDictionaryRef backup_new
= NULL
;
2061 keybag_handle_t bag_handle
;
2062 if (!ks_open_keybag(keybag
, password
, &bag_handle
, error
))
2065 // We need to have a datasource singleton for protection domain
2066 // kSecAttrAccessibleWhenUnlocked and keep a single shared engine
2067 // instance around which we create in the datasource constructor as well.
2068 SOSDataSourceFactoryRef dsf
= SecItemDataSourceFactoryGetDefault();
2069 SOSDataSourceRef ds
= SOSDataSourceFactoryCreateDataSource(dsf
, kSecAttrAccessibleWhenUnlocked
, error
);
2071 backup_new
= backup
? CFDictionaryCreateMutableCopy(kCFAllocatorDefault
, 0, backup
) : CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2072 mold
= SOSCreateManifestWithBackup(backup
, error
);
2073 SOSEngineRef engine
= SOSDataSourceGetSharedEngine(ds
, error
);
2074 mnow
= SOSEngineCopyManifest(engine
, NULL
);
2076 mnow
= SOSDataSourceCopyManifestWithViewNameSet(ds
, SOSViewsGetV0ViewSet(), error
);
2079 CFReleaseNull(backup_new
);
2080 secerror("failed to obtain manifest for keychain: %@", error
? *error
: NULL
);
2082 SOSManifestDiff(mold
, mnow
, &mdelete
, &madd
, error
);
2085 // Delete everything from the new_backup that is no longer in the datasource according to the datasources manifest.
2086 SOSManifestForEach(mdelete
, ^(CFDataRef digest_data
, bool *stop
) {
2087 CFStringRef deleted_item_key
= CFDataCopyHexString(digest_data
);
2088 CFDictionaryRemoveValue(backup_new
, deleted_item_key
);
2089 CFRelease(deleted_item_key
);
2092 CFMutableArrayRef changes
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
2093 SOSDataSourceForEachObject(ds
, madd
, error
, ^void(CFDataRef digest
, SOSObjectRef object
, bool *stop
) {
2094 CFErrorRef localError
= NULL
;
2095 CFDataRef digest_data
= NULL
;
2096 CFTypeRef value
= NULL
;
2098 // Key in our manifest can't be found in db, remove it from our manifest
2099 SOSChangesAppendDelete(changes
, digest
);
2100 } else if (!(digest_data
= SOSObjectCopyDigest(ds
, object
, &localError
))
2101 || !(value
= SOSObjectCopyBackup(ds
, object
, bag_handle
, &localError
))) {
2102 if (SecErrorGetOSStatus(localError
) == errSecDecode
) {
2103 // Ignore decode errors, pretend the objects aren't there
2104 CFRelease(localError
);
2105 // Object undecodable, remove it from our manifest
2106 SOSChangesAppendDelete(changes
, digest
);
2108 // Stop iterating and propagate out all other errors.
2110 *error
= localError
;
2111 CFReleaseNull(backup_new
);
2114 // TODO: Should we skip tombstones here?
2115 CFStringRef key
= CFDataCopyHexString(digest_data
);
2116 CFDictionarySetValue(backup_new
, key
, value
);
2119 CFReleaseSafe(digest_data
);
2120 CFReleaseSafe(value
);
2121 }) || CFReleaseNull(backup_new
);
2123 if (CFArrayGetCount(changes
)) {
2124 if (!SOSEngineUpdateChanges(engine
, kSOSDataSourceSOSTransaction
, changes
, error
)) {
2125 CFReleaseNull(backup_new
);
2128 CFReleaseSafe(changes
);
2130 SOSDataSourceRelease(ds
, error
) || CFReleaseNull(backup_new
);
2133 CFReleaseSafe(mold
);
2134 CFReleaseSafe(mnow
);
2135 CFReleaseSafe(madd
);
2136 CFReleaseSafe(mdelete
);
2137 ks_close_keybag(bag_handle
, error
) || CFReleaseNull(backup_new
);
2143 _SecServerRestoreTruthInTheCloud(CFDataRef keybag
, CFDataRef password
, CFDictionaryRef backup_in
, CFErrorRef
*error
) {
2144 __block
bool ok
= true;
2145 keybag_handle_t bag_handle
;
2146 if (!ks_open_keybag(keybag
, password
, &bag_handle
, error
))
2149 SOSManifestRef mbackup
= SOSCreateManifestWithBackup(backup_in
, error
);
2151 SOSDataSourceFactoryRef dsf
= SecItemDataSourceFactoryGetDefault();
2152 SOSDataSourceRef ds
= SOSDataSourceFactoryCreateDataSource(dsf
, kSecAttrAccessibleWhenUnlocked
, error
);
2153 ok
&= ds
&& SOSDataSourceWith(ds
, error
, ^(SOSTransactionRef txn
, bool *commit
) {
2154 SOSManifestRef mnow
= SOSDataSourceCopyManifestWithViewNameSet(ds
, SOSViewsGetV0BackupViewSet(), error
);
2155 SOSManifestRef mdelete
= NULL
, madd
= NULL
;
2156 SOSManifestDiff(mnow
, mbackup
, &mdelete
, &madd
, error
);
2158 // Don't delete everything in datasource not in backup.
2160 // Add items from the backup
2161 SOSManifestForEach(madd
, ^void(CFDataRef e
, bool *stop
) {
2162 CFDictionaryRef item
= NULL
;
2163 CFStringRef sha1
= CFDataCopyHexString(e
);
2165 item
= CFDictionaryGetValue(backup_in
, sha1
);
2169 CFErrorRef localError
= NULL
;
2171 if (!SOSObjectRestoreObject(ds
, txn
, bag_handle
, item
, &localError
)) {
2172 OSStatus status
= SecErrorGetOSStatus(localError
);
2173 if (status
== errSecDuplicateItem
) {
2174 // Log and ignore duplicate item errors during restore
2175 secnotice("titc", "restore %@ not replacing existing item", item
);
2176 } else if (status
== errSecDecode
) {
2177 // Log and ignore corrupted item errors during restore
2178 secnotice("titc", "restore %@ skipping corrupted item %@", item
, localError
);
2180 if (status
== errSecInteractionNotAllowed
)
2182 // Propagate the first other error upwards (causing the restore to fail).
2183 secerror("restore %@ failed %@", item
, localError
);
2185 if (error
&& !*error
) {
2186 *error
= localError
;
2190 CFReleaseSafe(localError
);
2194 ok
&= SOSDataSourceRelease(ds
, error
);
2195 CFReleaseNull(mdelete
);
2196 CFReleaseNull(madd
);
2197 CFReleaseNull(mnow
);
2202 ok
&= ks_close_keybag(bag_handle
, error
);
2208 CF_RETURNS_RETAINED CFDictionaryRef
2209 _SecServerBackupSyncable(CFDictionaryRef backup
, CFDataRef keybag
, CFDataRef password
, CFErrorRef
*error
) {
2210 require_action_quiet(isData(keybag
), errOut
, SecError(errSecParam
, error
, CFSTR("keybag %@ not a data"), keybag
));
2211 require_action_quiet(!backup
|| isDictionary(backup
), errOut
, SecError(errSecParam
, error
, CFSTR("backup %@ not a dictionary"), backup
));
2212 require_action_quiet(!password
|| isData(password
), errOut
, SecError(errSecParam
, error
, CFSTR("password %@ not a data"), password
));
2214 return _SecServerCopyTruthInTheCloud(keybag
, password
, backup
, error
);
2221 _SecServerRestoreSyncable(CFDictionaryRef backup
, CFDataRef keybag
, CFDataRef password
, CFErrorRef
*error
) {
2223 require_action_quiet(isData(keybag
), errOut
, ok
= SecError(errSecParam
, error
, CFSTR("keybag %@ not a data"), keybag
));
2224 require_action_quiet(isDictionary(backup
), errOut
, ok
= SecError(errSecParam
, error
, CFSTR("backup %@ not a dictionary"), backup
));
2227 require_action_quiet(isData(password
), errOut
, ok
= SecError(errSecParam
, error
, CFSTR("password not a data")));
2230 ok
= _SecServerRestoreTruthInTheCloud(keybag
, password
, backup
, error
);
2236 bool _SecServerRollKeysGlue(bool force
, CFErrorRef
*error
) {
2237 return _SecServerRollKeys(force
, NULL
, error
);
2241 bool _SecServerRollKeys(bool force
, SecurityClient
*client
, CFErrorRef
*error
) {
2243 uint32_t keystore_generation_status
= 0;
2244 if (aks_generation(KEYBAG_DEVICE
, generation_noop
, &keystore_generation_status
))
2246 uint32_t current_generation
= keystore_generation_status
& generation_current
;
2248 return kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
2249 bool up_to_date
= s3dl_dbt_keys_current(dbt
, current_generation
, NULL
);
2251 if (force
&& !up_to_date
) {
2252 up_to_date
= s3dl_dbt_update_keys(dbt
, client
, error
);
2254 secerror("Completed roll keys.");
2255 up_to_date
= s3dl_dbt_keys_current(dbt
, current_generation
, NULL
);
2258 secerror("Failed to roll keys.");
2270 * Sync bubble migration code
2273 struct SyncBubbleRule
{
2274 CFStringRef attribute
;
2279 TransmogrifyItemsToSyncBubble(SecurityClient
*client
, uid_t uid
,
2282 const SecDbClass
*qclass
,
2283 struct SyncBubbleRule
*items
, CFIndex nItems
,
2286 CFMutableDictionaryRef updateAttributes
= NULL
;
2287 CFDataRef syncBubbleView
= NULL
;
2288 CFDataRef activeUserView
= NULL
;
2293 syncBubbleView
= SecMUSRCreateSyncBubbleUserUUID(uid
);
2294 require(syncBubbleView
, fail
);
2296 activeUserView
= SecMUSRCreateActiveUserUUID(uid
);
2297 require(activeUserView
, fail
);
2300 if ((onlyDelete
&& !copyToo
) || !onlyDelete
) {
2303 * Clean out items first
2306 secnotice("syncbubble", "cleaning out old items");
2308 q
= query_create(qclass
, NULL
, NULL
, error
);
2311 q
->q_limit
= kSecMatchUnlimited
;
2312 q
->q_keybag
= device_keybag_handle
;
2314 for (n
= 0; n
< nItems
; n
++) {
2315 query_add_attribute(items
[n
].attribute
, items
[n
].value
, q
);
2317 q
->q_musrView
= CFRetain(syncBubbleView
);
2318 require(q
->q_musrView
, fail
);
2320 kc_with_dbt(false, error
, ^(SecDbConnectionRef dbt
) {
2321 return kc_transaction(dbt
, error
, ^{
2322 return s3dl_query_delete(dbt
, q
, NULL
, error
);
2326 query_destroy(q
, NULL
);
2331 if (onlyDelete
|| !copyToo
) {
2332 secnotice("syncbubble", "skip migration of items");
2335 * Copy over items from EMCS to sync bubble
2338 secnotice("syncbubble", "migrating sync bubble items");
2340 q
= query_create(qclass
, NULL
, NULL
, error
);
2343 q
->q_return_type
= kSecReturnDataMask
| kSecReturnAttributesMask
;
2344 q
->q_limit
= kSecMatchUnlimited
;
2345 q
->q_keybag
= device_keybag_handle
; /* XXX change to session key bag when it exists */
2347 for (n
= 0; n
< nItems
; n
++) {
2348 query_add_or_attribute(items
[n
].attribute
, items
[n
].value
, q
);
2350 query_add_or_attribute(CFSTR("musr"), activeUserView
, q
);
2351 q
->q_musrView
= CFRetain(activeUserView
);
2353 updateAttributes
= CFDictionaryCreateMutableForCFTypes(NULL
);
2354 require(updateAttributes
, fail
);
2356 CFDictionarySetValue(updateAttributes
, CFSTR("musr"), syncBubbleView
); /* XXX should use kSecAttrMultiUser */
2359 kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
2360 return kc_transaction(dbt
, error
, ^{
2361 CFErrorRef error2
= NULL
;
2363 SecDbItemSelect(q
, dbt
, &error2
, NULL
, ^bool(const SecDbAttr
*attr
) {
2364 return CFDictionaryGetValue(q
->q_item
, attr
->name
);
2365 }, NULL
, NULL
, ^(SecDbItemRef item
, bool *stop
) {
2366 CFErrorRef error3
= NULL
;
2367 secinfo("syncbubble", "migrating item");
2369 SecDbItemRef new_item
= SecDbItemCopyWithUpdates(item
, updateAttributes
, NULL
);
2370 if (new_item
== NULL
)
2373 SecDbItemClearRowId(new_item
, NULL
);
2375 if (!SecDbItemSetKeybag(new_item
, device_keybag_handle
, NULL
)) {
2376 CFRelease(new_item
);
2380 if (!SecDbItemInsert(new_item
, dbt
, &error3
)) {
2381 secnotice("syncbubble", "migration failed with %@ for item %@", error3
, new_item
);
2383 CFRelease(new_item
);
2384 CFReleaseNull(error3
);
2386 CFReleaseNull(error2
);
2395 CFReleaseNull(syncBubbleView
);
2396 CFReleaseNull(activeUserView
);
2397 CFReleaseNull(updateAttributes
);
2399 query_destroy(q
, NULL
);
2404 static struct SyncBubbleRule PCSItems
[] = {
2406 .attribute
= CFSTR("agrp"),
2407 .value
= CFSTR("com.apple.ProtectedCloudStorage"),
2410 static struct SyncBubbleRule NSURLSesssiond
[] = {
2412 .attribute
= CFSTR("agrp"),
2413 .value
= CFSTR("com.apple.nsurlsessiond"),
2416 static struct SyncBubbleRule AccountsdItems
[] = {
2418 .attribute
= CFSTR("svce"),
2419 .value
= CFSTR("com.apple.account.AppleAccount.token"),
2422 .attribute
= CFSTR("svce"),
2423 .value
= CFSTR("com.apple.account.AppleAccount.password"),
2426 .attribute
= CFSTR("svce"),
2427 .value
= CFSTR("com.apple.account.AppleAccount.rpassword"),
2430 .attribute
= CFSTR("svce"),
2431 .value
= CFSTR("com.apple.account.idms.token"),
2434 .attribute
= CFSTR("svce"),
2435 .value
= CFSTR("com.apple.account.idms.continuation-key"),
2438 .attribute
= CFSTR("svce"),
2439 .value
= CFSTR("com.apple.account.CloudKit.token"),
2443 static struct SyncBubbleRule MobileMailItems
[] = {
2445 .attribute
= CFSTR("svce"),
2446 .value
= CFSTR("com.apple.account.IMAP.password"),
2449 .attribute
= CFSTR("svce"),
2450 .value
= CFSTR("com.apple.account.SMTP.password"),
2453 .attribute
= CFSTR("svce"),
2454 .value
= CFSTR("com.apple.account.Exchange.password"),
2457 .attribute
= CFSTR("svce"),
2458 .value
= CFSTR("com.apple.account.Hotmail.password"),
2461 .attribute
= CFSTR("svce"),
2462 .value
= CFSTR("com.apple.account.Google.password"),
2465 .attribute
= CFSTR("svce"),
2466 .value
= CFSTR("com.apple.account.Google.oauth-token"),
2469 .attribute
= CFSTR("svce"),
2470 .value
= CFSTR("com.apple.account.Google.oath-refresh-token"),
2473 .attribute
= CFSTR("svce"),
2474 .value
= CFSTR("com.apple.account.Yahoo.password"),
2477 .attribute
= CFSTR("svce"),
2478 .value
= CFSTR("com.apple.account.Yahoo.oauth-token"),
2481 .attribute
= CFSTR("svce"),
2482 .value
= CFSTR("com.apple.account.Yahoo.oauth-token-nosync"),
2485 .attribute
= CFSTR("svce"),
2486 .value
= CFSTR("com.apple.account.Yahoo.oath-refresh-token"),
2489 .attribute
= CFSTR("svce"),
2490 .value
= CFSTR("com.apple.account.IMAPNotes.password"),
2493 .attribute
= CFSTR("svce"),
2494 .value
= CFSTR("com.apple.account.IMAPMail.password"),
2497 .attribute
= CFSTR("svce"),
2498 .value
= CFSTR("com.apple.account.126.password"),
2501 .attribute
= CFSTR("svce"),
2502 .value
= CFSTR("com.apple.account.163.password"),
2505 .attribute
= CFSTR("svce"),
2506 .value
= CFSTR("com.apple.account.aol.password"),
2511 ArrayContains(CFArrayRef array
, CFStringRef service
)
2513 return CFArrayContainsValue(array
, CFRangeMake(0, CFArrayGetCount(array
)), service
);
2517 _SecServerTransmogrifyToSyncBubble(CFArrayRef services
, uid_t uid
, SecurityClient
*client
, CFErrorRef
*error
)
2519 bool copyCloudAuthToken
= false;
2520 bool copyMobileMail
= false;
2522 bool copyPCS
= false;
2523 bool onlyDelete
= false;
2524 bool copyNSURLSesssion
= false;
2526 if (!client
->inMultiUser
)
2529 secnotice("syncbubble", "migration for uid %d uid for services %@", (int)uid
, services
);
2531 #if TARGET_OS_SIMULATOR
2534 if (uid
!= (uid_t
)client
->activeUser
)
2537 #error "no sync bubble on other platforms"
2541 * First select that services to copy/delete
2544 if (ArrayContains(services
, CFSTR("com.apple.bird.usermanager.sync"))
2545 || ArrayContains(services
, CFSTR("com.apple.cloudphotod.sync"))
2546 || ArrayContains(services
, CFSTR("com.apple.cloudphotod.syncstakeholder"))
2547 || ArrayContains(services
, CFSTR("com.apple.cloudd.usermanager.sync")))
2549 copyCloudAuthToken
= true;
2553 if (ArrayContains(services
, CFSTR("com.apple.nsurlsessiond.usermanager.sync")))
2555 copyCloudAuthToken
= true;
2556 copyNSURLSesssion
= true;
2559 if (ArrayContains(services
, CFSTR("com.apple.syncdefaultsd.usermanager.sync"))) {
2560 copyCloudAuthToken
= true;
2562 if (ArrayContains(services
, CFSTR("com.apple.mailq.sync")) || ArrayContains(services
, CFSTR("com.apple.mailq.sync.xpc"))) {
2563 copyCloudAuthToken
= true;
2564 copyMobileMail
= true;
2569 * The actually copy/delete the items selected
2572 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyPCS
, &inet_class
, PCSItems
, sizeof(PCSItems
)/sizeof(PCSItems
[0]), error
);
2574 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyPCS
, &genp_class
, PCSItems
, sizeof(PCSItems
)/sizeof(PCSItems
[0]), error
);
2578 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyMobileMail
, &genp_class
, MobileMailItems
, sizeof(MobileMailItems
)/sizeof(MobileMailItems
[0]), error
);
2582 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyCloudAuthToken
, &genp_class
, AccountsdItems
, sizeof(AccountsdItems
)/sizeof(AccountsdItems
[0]), error
);
2586 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyNSURLSesssion
, &inet_class
, NSURLSesssiond
, sizeof(NSURLSesssiond
)/sizeof(NSURLSesssiond
[0]), error
);
2594 * Migrate from user keychain to system keychain when switching to edu mode
2598 _SecServerTransmogrifyToSystemKeychain(SecurityClient
*client
, CFErrorRef
*error
)
2600 __block
bool ok
= true;
2603 * we are not in multi user yet, about to switch, otherwise we would
2604 * check that for client->inMultiuser here
2607 kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
2608 return kc_transaction(dbt
, error
, ^{
2609 CFDataRef systemUUID
= SecMUSRGetSystemKeychainUUID();
2611 const SecDbSchema
*newSchema
= kc_schemas
[0];
2612 SecDbClass
const *const *kcClass
;
2614 for (kcClass
= newSchema
->classes
; *kcClass
!= NULL
; kcClass
++) {
2615 CFErrorRef localError
= NULL
;
2618 if (*kcClass
== &tversion_class
|| *kcClass
== &identity_class
)
2621 q
= query_create(*kcClass
, SecMUSRGetSingleUserKeychainUUID(), NULL
, error
);
2625 ok
&= SecDbItemSelect(q
, dbt
, error
, ^bool(const SecDbAttr
*attr
) {
2626 return (attr
->flags
& kSecDbInFlag
) != 0;
2627 }, ^bool(const SecDbAttr
*attr
) {
2628 // No filtering please.
2630 }, ^bool(CFMutableStringRef sql
, bool *needWhere
) {
2631 SecDbAppendWhereOrAnd(sql
, needWhere
);
2632 CFStringAppendFormat(sql
, NULL
, CFSTR("musr = ?"));
2634 }, ^bool(sqlite3_stmt
*stmt
, int col
) {
2635 return SecDbBindObject(stmt
, col
++, SecMUSRGetSingleUserKeychainUUID(), error
);
2636 }, ^(SecDbItemRef item
, bool *stop
) {
2637 CFErrorRef localError
= NULL
;
2639 if (!SecDbItemSetValueWithName(item
, kSecAttrMultiUser
, systemUUID
, &localError
)) {
2640 secerror("item: %@ update musr to system failed: %@", item
, localError
);
2645 if (!SecDbItemDoUpdate(item
, item
, dbt
, &localError
, ^bool (const SecDbAttr
*attr
) {
2646 return attr
->kind
== kSecDbRowIdAttr
;
2648 secerror("item: %@ insert during UPDATE: %@", item
, localError
);
2654 SecErrorPropagate(localError
, error
);
2655 CFReleaseSafe(localError
);
2659 query_destroy(q
, &localError
);
2670 * Migrate from user keychain to system keychain when switching to edu mode
2674 _SecServerDeleteMUSERViews(SecurityClient
*client
, uid_t uid
, CFErrorRef
*error
)
2676 return kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
2677 CFDataRef musrView
= NULL
, syncBubbleView
= NULL
;
2680 syncBubbleView
= SecMUSRCreateSyncBubbleUserUUID(uid
);
2681 require(syncBubbleView
, fail
);
2683 musrView
= SecMUSRCreateActiveUserUUID(uid
);
2684 require(musrView
, fail
);
2686 require(ok
= SecServerDeleteAllForUser(dbt
, syncBubbleView
, error
), fail
);
2687 require(ok
= SecServerDeleteAllForUser(dbt
, musrView
, error
), fail
);
2690 CFReleaseNull(syncBubbleView
);
2691 CFReleaseNull(musrView
);
2697 #endif /* TARGET_OS_IOS */
2700 _SecServerGetKeyStats(const SecDbClass
*qclass
,
2701 struct _SecServerKeyStats
*stats
)
2703 __block CFErrorRef error
= NULL
;
2706 Query
*q
= query_create(qclass
, NULL
, NULL
, &error
);
2709 q
->q_return_type
= kSecReturnDataMask
| kSecReturnAttributesMask
;
2710 q
->q_limit
= kSecMatchUnlimited
;
2711 q
->q_keybag
= KEYBAG_DEVICE
;
2712 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleWhenUnlocked
, q
);
2713 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleAfterFirstUnlock
, q
);
2714 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleAlways
, q
);
2715 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleWhenUnlockedThisDeviceOnly
, q
);
2716 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
, q
);
2717 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleAlwaysThisDeviceOnly
, q
);
2718 query_add_attribute(kSecAttrTombstone
, kCFBooleanFalse
, q
);
2720 kc_with_dbt(false, &error
, ^(SecDbConnectionRef dbconn
) {
2721 CFErrorRef error2
= NULL
;
2722 __block CFIndex totalSize
= 0;
2723 stats
->maxDataSize
= 0;
2725 SecDbItemSelect(q
, dbconn
, &error2
, NULL
, ^bool(const SecDbAttr
*attr
) {
2726 return CFDictionaryContainsKey(q
->q_item
, attr
->name
);
2727 }, NULL
, NULL
, ^(SecDbItemRef item
, bool *stop
) {
2728 CFErrorRef error3
= NULL
;
2729 CFDataRef data
= SecDbItemGetValue(item
, &v6v_Data
, &error3
);
2731 CFIndex size
= CFDataGetLength(data
);
2732 if (size
> stats
->maxDataSize
)
2733 stats
->maxDataSize
= size
;
2737 CFReleaseNull(error3
);
2739 CFReleaseNull(error2
);
2741 stats
->averageSize
= totalSize
/ stats
->items
;
2750 CFReleaseNull(error
);
2752 query_destroy(q
, NULL
);