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_WATCH
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 kc_transaction(dbt
, error
, ^{
1123 return s3dl_query_update(dbt
, q
, attributesToUpdate
, accessGroups
, error
);
1128 ok
= query_notify_and_destroy(q
, ok
, error
);
1134 /* AUDIT[securityd](done):
1135 query (ok) is a caller provided dictionary, only its cf type has been checked.
1138 _SecItemDelete(CFDictionaryRef query
, SecurityClient
*client
, CFErrorRef
*error
)
1140 CFArrayRef accessGroups
= client
->accessGroups
;
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"));
1150 if (CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), CFSTR("*"))) {
1151 /* Having the special accessGroup "*" allows access to all accessGroups. */
1152 accessGroups
= NULL
;
1155 Query
*q
= query_create_with_limit(query
, client
->musr
, kSecMatchUnlimited
, error
);
1158 #if TARGET_OS_IPHONE
1159 if (q
->q_system_keychain
&& client
->inMultiUser
) {
1160 CFReleaseNull(q
->q_musrView
);
1161 q
->q_musrView
= SecMUSRCopySystemKeychainUUID();
1163 q
->q_system_keychain
= false;
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"));
1182 ok
= kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
1183 return kc_transaction(dbt
, error
, ^{
1184 return s3dl_query_delete(dbt
, q
, accessGroups
, error
);
1188 ok
= query_notify_and_destroy(q
, ok
, error
);
1196 /* AUDIT[securityd](done):
1197 No caller provided inputs.
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
));
1212 _SecItemDeleteAll(CFErrorRef
*error
) {
1213 return SecItemServerDeleteAll(error
);
1218 // MARK: Shared web credentials
1221 #define SEC_CONST_DECL(k,v) const CFStringRef k = CFSTR(v);
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");
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
;
1235 static OSStatus
_SecSWCEnsuredInitialized(void);
1237 static OSStatus
_SecSWCEnsuredInitialized(void)
1239 __block OSStatus status
= errSecNotAvailable
;
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");
1250 if (sSWCCheckService_f
&& sSWCSetServiceFlags_f
) {
1257 #if !TARGET_IPHONE_SIMULATOR
1259 _SecAppDomainApprovalStatus(CFStringRef appID
, CFStringRef fqdn
, CFErrorRef
*error
)
1261 __block SWCFlags flags
= kSWCFlags_None
;
1263 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1264 OSStatus status
= _SecSWCEnsuredInitialized();
1266 SecError(status
, error
, CFSTR("SWC initialize failed"));
1269 CFRetainSafe(appID
);
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);
1283 // wait for the block to complete, as we need its answer
1284 dispatch_semaphore_wait(semaphore
, DISPATCH_TIME_FOREVER
);
1286 else // didn't queue the block
1288 CFReleaseSafe(appID
);
1289 CFReleaseSafe(fqdn
);
1290 dispatch_release(semaphore
);
1292 dispatch_release(semaphore
);
1294 flags
|= (kSWCFlag_SiteApproved
);
1297 if (!error
) { return flags
; }
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
);
1305 SecError(errSecAuthFailed
, error
, CFSTR("\"%@\" failed to approve \"%@\""), fqdn
, appID
);
1310 // check user approval status
1311 if (flags
& kSWCFlag_UserDenied
) {
1312 SecError(errSecAuthFailed
, error
, CFSTR("User denied access to \"%@\" by \"%@\""), fqdn
, appID
);
1318 #if !TARGET_IPHONE_SIMULATOR
1320 _SecEntitlementContainsDomainForService(CFArrayRef domains
, CFStringRef domain
, CFStringRef service
)
1322 bool result
= false;
1323 CFIndex idx
, count
= (domains
) ? CFArrayGetCount(domains
) : (CFIndex
) 0;
1324 if (!count
|| !domain
|| !service
) {
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
)) {
1337 CFReleaseSafe(substr
);
1348 _SecAddNegativeWebCredential(SecurityClient
*client
, CFStringRef fqdn
, CFStringRef appID
, bool forSafari
)
1350 bool result
= false;
1351 if (!fqdn
) { return result
; }
1353 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1354 OSStatus status
= _SecSWCEnsuredInitialized();
1355 if (status
) { return false; }
1357 // update our database
1358 CFRetainSafe(appID
);
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
);
1369 else // didn't queue the block
1371 CFReleaseSafe(appID
);
1372 CFReleaseSafe(fqdn
);
1375 if (!forSafari
) { return result
; }
1377 // below this point: create a negative Safari web credential item
1379 CFMutableDictionaryRef attrs
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1380 if (!attrs
) { return result
; }
1382 CFErrorRef error
= NULL
;
1383 CFStringRef accessGroup
= CFSTR("*");
1384 SecurityClient swcclient
= {
1386 .accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&accessGroup
, 1, &kCFTypeArrayCallBacks
),
1387 .allowSystemKeychain
= false,
1388 .allowSyncBubbleKeychain
= false,
1389 .isNetworkExtension
= false,
1390 .musr
= client
->musr
,
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
);
1400 (void)_SecItemDelete(attrs
, &swcclient
, &error
);
1401 CFReleaseNull(error
);
1403 CFDictionaryAddValue(attrs
, kSecAttrAccount
, kSecSafariPasswordsNotSaved
);
1404 CFDictionaryAddValue(attrs
, kSecAttrComment
, kSecSafariDefaultComment
);
1406 CFStringRef label
= CFStringCreateWithFormat(kCFAllocatorDefault
,
1407 NULL
, CFSTR("%@ (%@)"), fqdn
, kSecSafariPasswordsNotSaved
);
1409 CFDictionaryAddValue(attrs
, kSecAttrLabel
, label
);
1410 CFReleaseSafe(label
);
1414 CFDataRef data
= CFDataCreate(kCFAllocatorDefault
, &space
, 1);
1416 CFDictionarySetValue(attrs
, kSecValueData
, data
);
1417 CFReleaseSafe(data
);
1420 CFTypeRef addResult
= NULL
;
1421 result
= _SecItemAdd(attrs
, &swcclient
, &addResult
, &error
);
1423 CFReleaseSafe(addResult
);
1424 CFReleaseSafe(error
);
1425 CFReleaseSafe(attrs
);
1426 CFReleaseSafe(swcclient
.accessGroups
);
1431 /* Specialized version of SecItemAdd for shared web credentials */
1433 _SecAddSharedWebCredential(CFDictionaryRef attributes
,
1434 SecurityClient
*client
,
1435 const audit_token_t
*clientAuditToken
,
1442 SecurityClient swcclient
= {};
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
;
1452 // check autofill enabled status
1453 if (!swca_autofill_enabled(clientAuditToken
)) {
1454 SecError(errSecBadReq
, error
, CFSTR("Autofill is not enabled in Safari settings"));
1458 // parse fqdn with CFURL here, since it could be specified as domain:port
1460 CFStringRef urlStr
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%@%@"), kSecSharedCredentialUrlScheme
, fqdn
);
1462 CFURLRef url
= CFURLCreateWithString(kCFAllocatorDefault
, urlStr
, nil
);
1464 CFStringRef hostname
= CFURLCopyHostName(url
);
1466 CFReleaseSafe(fqdn
);
1468 port
= CFURLGetPortNumber(url
);
1472 CFReleaseSafe(urlStr
);
1477 SecError(errSecParam
, error
, CFSTR("No account provided"));
1481 SecError(errSecParam
, error
, CFSTR("No domain provided"));
1485 #if TARGET_IPHONE_SIMULATOR
1486 secerror("app/site association entitlements not checked in Simulator");
1488 OSStatus status
= errSecMissingEntitlement
;
1489 // validate that fqdn is part of caller's shared credential domains entitlement
1491 SecError(status
, error
, CFSTR("Missing application-identifier entitlement"));
1494 if (_SecEntitlementContainsDomainForService(domains
, fqdn
, kSecSharedWebCredentialsService
)) {
1495 status
= errSecSuccess
;
1497 if (errSecSuccess
!= status
) {
1498 CFStringRef msg
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
,
1499 CFSTR("%@ not found in %@ entitlement"), fqdn
, kSecEntitlementAssociatedDomains
);
1501 msg
= CFRetain(CFSTR("Requested domain not found in entitlement"));
1503 SecError(status
, error
, CFSTR("%@"), msg
);
1509 #if TARGET_IPHONE_SIMULATOR
1510 secerror("Ignoring app/site approval state in the Simulator.");
1512 // get approval status for this app/domain pair
1513 SWCFlags flags
= _SecAppDomainApprovalStatus(appID
, fqdn
, error
);
1514 if (!(flags
& kSWCFlag_SiteApproved
)) {
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;
1529 // create lookup query
1530 query
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1532 SecError(errSecAllocate
, error
, CFSTR("Unable to create query dictionary"));
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
);
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
);
1547 SecError(errSecDuplicateItem
, error
, CFSTR("Item already exists for this server"));
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
);
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
);
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
);
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); });
1578 ok
= _SecItemUpdate(query
, attrs
, &swcclient
, error
);
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); });
1587 ok
= _SecItemDelete(query
, &swcclient
, error
);
1591 CFReleaseNull(*error
);
1595 if(result
) CFReleaseNull(*result
);
1596 CFReleaseNull(*error
);
1598 // password does not exist, so prepare to add it
1600 // a NULL password value removes the existing credential. Since we didn't find it, this is a no-op.
1605 CFStringRef label
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%@ (%@)"), fqdn
, account
);
1607 CFDictionaryAddValue(query
, kSecAttrLabel
, label
);
1608 CFReleaseSafe(label
);
1610 // NOTE: we always expect to use HTTPS for web forms.
1611 CFDictionaryAddValue(query
, kSecAttrProtocol
, kSecAttrProtocolHTTPS
);
1613 CFDataRef credential
= CFStringCreateExternalRepresentation(kCFAllocatorDefault
, password
, kCFStringEncodingUTF8
, 0);
1614 CFDictionarySetValue(query
, kSecValueData
, credential
);
1615 CFReleaseSafe(credential
);
1616 CFDictionarySetValue(query
, kSecAttrComment
, kSecSafariDefaultComment
);
1618 CFReleaseSafe(swcclient
.accessGroups
);
1619 swcclient
.accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&kSecSafariAccessGroup
, 1, &kCFTypeArrayCallBacks
);
1621 // mark the item as created by this function
1622 const int32_t creator_value
= 'swca';
1623 CFNumberRef creator
= CFNumberCreate(kCFAllocatorDefault
, kCFNumberSInt32Type
, &creator_value
);
1625 CFDictionarySetValue(query
, kSecAttrCreator
, creator
);
1626 CFReleaseSafe(creator
);
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); });
1637 ok
= _SecItemAdd(query
, &swcclient
, result
, error
);
1641 CFReleaseSafe(attrs
);
1642 CFReleaseSafe(query
);
1643 CFReleaseSafe(swcclient
.accessGroups
);
1644 CFReleaseSafe(fqdn
);
1648 /* Specialized version of SecItemCopyMatching for shared web credentials */
1650 _SecCopySharedWebCredential(CFDictionaryRef query
,
1651 SecurityClient
*client
,
1652 const audit_token_t
*clientAuditToken
,
1658 CFMutableArrayRef credentials
= NULL
;
1659 CFMutableArrayRef foundItems
= NULL
;
1660 CFMutableArrayRef fqdns
= NULL
;
1661 CFStringRef fqdn
= NULL
;
1662 CFStringRef account
= NULL
;
1667 require_quiet(result
, cleanup
);
1668 credentials
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
1669 foundItems
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
1670 fqdns
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
1672 // give ourselves access to see matching items for kSecSafariAccessGroup
1673 CFStringRef accessGroup
= CFSTR("*");
1674 SecurityClient swcclient
= {
1676 .accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&accessGroup
, 1, &kCFTypeArrayCallBacks
),
1677 .allowSystemKeychain
= false,
1678 .allowSyncBubbleKeychain
= false,
1679 .isNetworkExtension
= false,
1680 .musr
= client
->musr
,
1683 // On input, the query dictionary contains optional fqdn and account entries.
1684 fqdn
= CFDictionaryGetValue(query
, kSecAttrServer
);
1685 account
= CFDictionaryGetValue(query
, kSecAttrAccount
);
1687 // Check autofill enabled status
1688 if (!swca_autofill_enabled(clientAuditToken
)) {
1689 SecError(errSecBadReq
, error
, CFSTR("Autofill is not enabled in Safari settings"));
1693 // Check fqdn; if NULL, add domains from caller's entitlement.
1695 CFArrayAppendValue(fqdns
, fqdn
);
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
);
1708 CFArrayAppendValue(fqdns
, fqdn
);
1714 count
= CFArrayGetCount(fqdns
);
1716 SecError(errSecParam
, error
, CFSTR("No domain provided"));
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
);
1728 // Parse the fqdn for a possible port specifier.
1730 CFStringRef urlStr
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%@%@"), kSecSharedCredentialUrlScheme
, fqdn
);
1732 CFURLRef url
= CFURLCreateWithString(kCFAllocatorDefault
, urlStr
, nil
);
1734 CFStringRef hostname
= CFURLCopyHostName(url
);
1736 CFReleaseSafe(fqdn
);
1738 port
= CFURLGetPortNumber(url
);
1742 CFReleaseSafe(urlStr
);
1746 #if TARGET_IPHONE_SIMULATOR
1747 secerror("app/site association entitlements not checked in Simulator");
1749 OSStatus status
= errSecMissingEntitlement
;
1751 SecError(status
, error
, CFSTR("Missing application-identifier entitlement"));
1752 CFReleaseSafe(fqdn
);
1755 // validate that fqdn is part of caller's entitlement
1756 if (_SecEntitlementContainsDomainForService(domains
, fqdn
, kSecSharedWebCredentialsService
)) {
1757 status
= errSecSuccess
;
1759 if (errSecSuccess
!= status
) {
1760 CFStringRef msg
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
,
1761 CFSTR("%@ not found in %@ entitlement"), fqdn
, kSecEntitlementAssociatedDomains
);
1763 msg
= CFRetain(CFSTR("Requested domain not found in entitlement"));
1765 SecError(status
, error
, CFSTR("%@"), msg
);
1767 CFReleaseSafe(fqdn
);
1772 attrs
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1774 SecError(errSecAllocate
, error
, CFSTR("Unable to create query dictionary"));
1775 CFReleaseSafe(fqdn
);
1778 CFDictionaryAddValue(attrs
, kSecClass
, kSecClassInternetPassword
);
1779 CFDictionaryAddValue(attrs
, kSecAttrAccessGroup
, kSecSafariAccessGroup
);
1780 CFDictionaryAddValue(attrs
, kSecAttrAuthenticationType
, kSecAttrAuthenticationTypeHTMLForm
);
1781 CFDictionaryAddValue(attrs
, kSecAttrServer
, fqdn
);
1783 CFDictionaryAddValue(attrs
, kSecAttrAccount
, account
);
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
);
1791 CFDictionaryAddValue(attrs
, kSecAttrSynchronizable
, kCFBooleanTrue
);
1792 CFDictionaryAddValue(attrs
, kSecMatchLimit
, kSecMatchLimitAll
);
1793 CFDictionaryAddValue(attrs
, kSecReturnAttributes
, kCFBooleanTrue
);
1794 CFDictionaryAddValue(attrs
, kSecReturnData
, kCFBooleanTrue
);
1796 ok
= _SecItemCopyMatching(attrs
, &swcclient
, (CFTypeRef
*)&items
, error
);
1798 // ignore interim error since we have multiple domains to search
1799 CFReleaseNull(*error
);
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;
1806 // get approval status for this app/domain pair
1807 SWCFlags flags
= _SecAppDomainApprovalStatus(appID
, fqdn
, error
);
1809 // ignore interim error since we have multiple domains to check
1810 CFReleaseNull(*error
);
1812 bool approved
= (flags
& kSWCFlag_SiteApproved
);
1815 CFArrayAppendArray(foundItems
, items
, CFRangeMake(0, CFArrayGetCount(items
)));
1818 CFReleaseSafe(items
);
1819 CFReleaseSafe(attrs
);
1820 CFReleaseSafe(fqdn
);
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)
1830 // key: kSecAttrPort value: CFNumberRef (the port number, if non-standard for https)
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
);
1843 CFDictionaryAddValue(newdict
, kSecAttrServer
, srvr
);
1846 CFDictionaryAddValue(newdict
, kSecAttrAccount
, acct
);
1850 if (CFNumberGetValue(pnum
, kCFNumberSInt16Type
, &pval
) &&
1851 (pval
< -1 || pval
> 0)) {
1852 CFDictionaryAddValue(newdict
, kSecAttrPort
, pnum
);
1856 CFStringRef password
= CFStringCreateFromExternalRepresentation(kCFAllocatorDefault
, data
, kCFStringEncodingUTF8
);
1858 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1859 CFDictionaryAddValue(newdict
, kSecSharedPassword
, password
);
1861 CFDictionaryAddValue(newdict
, CFSTR("spwd"), password
);
1863 CFReleaseSafe(password
);
1866 if (icmt
&& CFEqual(icmt
, kSecSafariDefaultComment
)) {
1867 CFArrayInsertValueAtIndex(credentials
, 0, newdict
);
1869 CFArrayAppendValue(credentials
, newdict
);
1872 CFReleaseSafe(newdict
);
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
);
1888 CFDictionaryRemoveValue(newdict
, CFSTR("spwd"));
1890 CFArrayAppendValue(items
, newdict
);
1891 CFReleaseSafe(newdict
);
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
);
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
);
1908 if (!srvr
|| !srvr1
|| !CFEqual(srvr
, srvr1
)) continue;
1909 if (!acct
|| !acct1
|| !CFEqual(acct
, acct1
)) continue;
1910 if ((pnum
&& pnum1
) && !CFEqual(pnum
, pnum1
)) continue;
1913 CFReleaseSafe(selected
);
1920 CFReleaseSafe(items
);
1921 CFArrayRemoveAllValues(credentials
);
1922 if (selected
&& ok
) {
1923 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1924 fqdn
= CFDictionaryGetValue(selected
, kSecAttrServer
);
1926 CFArrayAppendValue(credentials
, selected
);
1930 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1931 // register confirmation with database
1932 OSStatus status
= _SecSWCEnsuredInitialized();
1934 SecError(status
, error
, CFSTR("SWC initialize failed"));
1936 CFReleaseSafe(selected
);
1939 CFRetainSafe(appID
);
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
);
1948 // we didn't queue the block
1949 CFReleaseSafe(appID
);
1950 CFReleaseSafe(fqdn
);
1954 CFReleaseSafe(selected
);
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"));
1963 CFArrayRemoveAllValues(credentials
);
1965 CFReleaseSafe(foundItems
);
1966 *result
= credentials
;
1967 CFReleaseSafe(swcclient
.accessGroups
);
1968 CFReleaseSafe(fqdns
);
1974 // MARK: Keychain backup
1976 CF_RETURNS_RETAINED CFDataRef
1977 _SecServerKeychainCreateBackup(SecurityClient
*client
, CFDataRef keybag
, CFDataRef passcode
, CFErrorRef
*error
) {
1979 SecDbConnectionRef dbt
= SecDbConnectionAquire(kc_dbhandle(), false, error
);
1984 if (keybag
== NULL
&& passcode
== NULL
) {
1986 backup
= SecServerExportBackupableKeychain(dbt
, client
, KEYBAG_DEVICE
, backup_keybag_handle
, error
);
1987 #else /* !USE_KEYSTORE */
1989 SecError(errSecParam
, error
, CFSTR("Why are you doing this?"));
1991 #endif /* USE_KEYSTORE */
1993 backup
= SecServerKeychainCreateBackup(dbt
, client
, keybag
, passcode
, error
);
1996 SecDbConnectionRelease(dbt
);
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"));
2006 __block
bool ok
= true;
2007 ok
&= SecDbPerformWrite(kc_dbhandle(), error
, ^(SecDbConnectionRef dbconn
) {
2008 ok
= SecServerKeychainRestore(dbconn
, client
, backup
, keybag
, passcode
, error
);
2012 SecKeychainChanged(true);
2020 // MARK: SecItemDataSource
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());
2028 /* AUDIT[securityd]:
2029 args_in (ok) is a caller provided, CFDictionaryRef.
2032 CF_RETURNS_RETAINED CFArrayRef
2033 _SecServerKeychainSyncUpdateMessage(CFDictionaryRef updates
, CFErrorRef
*error
) {
2034 // This never fails, trust us!
2035 return SOSCCHandleUpdateMessage(updates
);
2039 // Truthiness in the cloud backup/restore support.
2042 static CFDictionaryRef
2043 _SecServerCopyTruthInTheCloud(CFDataRef keybag
, CFDataRef password
,
2044 CFDictionaryRef backup
, CFErrorRef
*error
)
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
))
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
);
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
);
2063 mnow
= SOSDataSourceCopyManifestWithViewNameSet(ds
, SOSViewsGetV0ViewSet(), error
);
2066 CFReleaseNull(backup_new
);
2067 secerror("failed to obtain manifest for keychain: %@", error
? *error
: NULL
);
2069 SOSManifestDiff(mold
, mnow
, &mdelete
, &madd
, error
);
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
);
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
;
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
);
2095 // Stop iterating and propagate out all other errors.
2097 *error
= localError
;
2098 CFReleaseNull(backup_new
);
2101 // TODO: Should we skip tombstones here?
2102 CFStringRef key
= CFDataCopyHexString(digest_data
);
2103 CFDictionarySetValue(backup_new
, key
, value
);
2106 CFReleaseSafe(digest_data
);
2107 CFReleaseSafe(value
);
2108 }) || CFReleaseNull(backup_new
);
2110 if (CFArrayGetCount(changes
)) {
2111 if (!SOSEngineUpdateChanges(engine
, kSOSDataSourceSOSTransaction
, changes
, error
)) {
2112 CFReleaseNull(backup_new
);
2115 CFReleaseSafe(changes
);
2117 SOSDataSourceRelease(ds
, error
) || CFReleaseNull(backup_new
);
2120 CFReleaseSafe(mold
);
2121 CFReleaseSafe(mnow
);
2122 CFReleaseSafe(madd
);
2123 CFReleaseSafe(mdelete
);
2124 ks_close_keybag(bag_handle
, error
) || CFReleaseNull(backup_new
);
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
))
2136 SOSManifestRef mbackup
= SOSCreateManifestWithBackup(backup_in
, error
);
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
);
2145 // Don't delete everything in datasource not in backup.
2147 // Add items from the backup
2148 SOSManifestForEach(madd
, ^void(CFDataRef e
, bool *stop
) {
2149 CFDictionaryRef item
= NULL
;
2150 CFStringRef sha1
= CFDataCopyHexString(e
);
2152 item
= CFDictionaryGetValue(backup_in
, sha1
);
2156 CFErrorRef localError
= NULL
;
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
);
2167 if (status
== errSecInteractionNotAllowed
)
2169 // Propagate the first other error upwards (causing the restore to fail).
2170 secerror("restore %@ failed %@", item
, localError
);
2172 if (error
&& !*error
) {
2173 *error
= localError
;
2177 CFReleaseSafe(localError
);
2181 ok
&= SOSDataSourceRelease(ds
, error
);
2182 CFReleaseNull(mdelete
);
2183 CFReleaseNull(madd
);
2184 CFReleaseNull(mnow
);
2189 ok
&= ks_close_keybag(bag_handle
, error
);
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
));
2201 return _SecServerCopyTruthInTheCloud(keybag
, password
, backup
, error
);
2208 _SecServerRestoreSyncable(CFDictionaryRef backup
, CFDataRef keybag
, CFDataRef password
, CFErrorRef
*error
) {
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
));
2214 require_action_quiet(isData(password
), errOut
, ok
= SecError(errSecParam
, error
, CFSTR("password not a data")));
2217 ok
= _SecServerRestoreTruthInTheCloud(keybag
, password
, backup
, error
);
2223 bool _SecServerRollKeysGlue(bool force
, CFErrorRef
*error
) {
2224 return _SecServerRollKeys(force
, NULL
, error
);
2228 bool _SecServerRollKeys(bool force
, SecurityClient
*client
, CFErrorRef
*error
) {
2230 uint32_t keystore_generation_status
= 0;
2231 if (aks_generation(KEYBAG_DEVICE
, generation_noop
, &keystore_generation_status
))
2233 uint32_t current_generation
= keystore_generation_status
& generation_current
;
2235 return kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
2236 bool up_to_date
= s3dl_dbt_keys_current(dbt
, current_generation
, NULL
);
2238 if (force
&& !up_to_date
) {
2239 up_to_date
= s3dl_dbt_update_keys(dbt
, client
, error
);
2241 secerror("Completed roll keys.");
2242 up_to_date
= s3dl_dbt_keys_current(dbt
, current_generation
, NULL
);
2245 secerror("Failed to roll keys.");
2257 * Sync bubble migration code
2260 struct SyncBubbleRule
{
2261 CFStringRef attribute
;
2266 TransmogrifyItemsToSyncBubble(SecurityClient
*client
, uid_t uid
,
2269 const SecDbClass
*qclass
,
2270 struct SyncBubbleRule
*items
, CFIndex nItems
,
2273 CFMutableDictionaryRef updateAttributes
= NULL
;
2274 CFDataRef syncBubbleView
= NULL
;
2275 CFDataRef activeUserView
= NULL
;
2280 syncBubbleView
= SecMUSRCreateSyncBubbleUserUUID(uid
);
2281 require(syncBubbleView
, fail
);
2283 activeUserView
= SecMUSRCreateActiveUserUUID(uid
);
2284 require(activeUserView
, fail
);
2287 if ((onlyDelete
&& !copyToo
) || !onlyDelete
) {
2290 * Clean out items first
2293 secnotice("syncbubble", "cleaning out old items");
2295 q
= query_create(qclass
, NULL
, NULL
, error
);
2298 q
->q_limit
= kSecMatchUnlimited
;
2299 q
->q_keybag
= device_keybag_handle
;
2301 for (n
= 0; n
< nItems
; n
++) {
2302 query_add_attribute(items
[n
].attribute
, items
[n
].value
, q
);
2304 q
->q_musrView
= CFRetain(syncBubbleView
);
2305 require(q
->q_musrView
, fail
);
2307 kc_with_dbt(false, error
, ^(SecDbConnectionRef dbt
) {
2308 return kc_transaction(dbt
, error
, ^{
2309 return s3dl_query_delete(dbt
, q
, NULL
, error
);
2313 query_destroy(q
, NULL
);
2318 if (onlyDelete
|| !copyToo
) {
2319 secnotice("syncbubble", "skip migration of items");
2322 * Copy over items from EMCS to sync bubble
2325 secnotice("syncbubble", "migrating sync bubble items");
2327 q
= query_create(qclass
, NULL
, NULL
, error
);
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 */
2334 for (n
= 0; n
< nItems
; n
++) {
2335 query_add_or_attribute(items
[n
].attribute
, items
[n
].value
, q
);
2337 query_add_or_attribute(CFSTR("musr"), activeUserView
, q
);
2338 q
->q_musrView
= CFRetain(activeUserView
);
2340 updateAttributes
= CFDictionaryCreateMutableForCFTypes(NULL
);
2341 require(updateAttributes
, fail
);
2343 CFDictionarySetValue(updateAttributes
, CFSTR("musr"), syncBubbleView
); /* XXX should use kSecAttrMultiUser */
2346 kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
2347 return kc_transaction(dbt
, error
, ^{
2348 CFErrorRef error2
= NULL
;
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");
2356 SecDbItemRef new_item
= SecDbItemCopyWithUpdates(item
, updateAttributes
, NULL
);
2357 if (new_item
== NULL
)
2360 SecDbItemClearRowId(new_item
, NULL
);
2362 if (!SecDbItemSetKeybag(new_item
, device_keybag_handle
, NULL
)) {
2363 CFRelease(new_item
);
2367 if (!SecDbItemInsert(new_item
, dbt
, &error3
)) {
2368 secnotice("syncbubble", "migration failed with %@ for item %@", error3
, new_item
);
2370 CFRelease(new_item
);
2371 CFReleaseNull(error3
);
2373 CFReleaseNull(error2
);
2382 CFReleaseNull(syncBubbleView
);
2383 CFReleaseNull(activeUserView
);
2384 CFReleaseNull(updateAttributes
);
2386 query_destroy(q
, NULL
);
2391 static struct SyncBubbleRule PCSItems
[] = {
2393 .attribute
= CFSTR("agrp"),
2394 .value
= CFSTR("com.apple.ProtectedCloudStorage"),
2397 static struct SyncBubbleRule NSURLSesssiond
[] = {
2399 .attribute
= CFSTR("agrp"),
2400 .value
= CFSTR("com.apple.nsurlsessiond"),
2403 static struct SyncBubbleRule AccountsdItems
[] = {
2405 .attribute
= CFSTR("svce"),
2406 .value
= CFSTR("com.apple.account.AppleAccount.token"),
2409 .attribute
= CFSTR("svce"),
2410 .value
= CFSTR("com.apple.account.AppleAccount.password"),
2413 .attribute
= CFSTR("svce"),
2414 .value
= CFSTR("com.apple.account.AppleAccount.rpassword"),
2417 .attribute
= CFSTR("svce"),
2418 .value
= CFSTR("com.apple.account.idms.token"),
2421 .attribute
= CFSTR("svce"),
2422 .value
= CFSTR("com.apple.account.idms.continuation-key"),
2425 .attribute
= CFSTR("svce"),
2426 .value
= CFSTR("com.apple.account.CloudKit.token"),
2430 static struct SyncBubbleRule MobileMailItems
[] = {
2432 .attribute
= CFSTR("svce"),
2433 .value
= CFSTR("com.apple.account.IMAP.password"),
2436 .attribute
= CFSTR("svce"),
2437 .value
= CFSTR("com.apple.account.SMTP.password"),
2440 .attribute
= CFSTR("svce"),
2441 .value
= CFSTR("com.apple.account.Exchange.password"),
2444 .attribute
= CFSTR("svce"),
2445 .value
= CFSTR("com.apple.account.Hotmail.password"),
2448 .attribute
= CFSTR("svce"),
2449 .value
= CFSTR("com.apple.account.Google.password"),
2452 .attribute
= CFSTR("svce"),
2453 .value
= CFSTR("com.apple.account.Google.oauth-token"),
2456 .attribute
= CFSTR("svce"),
2457 .value
= CFSTR("com.apple.account.Google.oath-refresh-token"),
2460 .attribute
= CFSTR("svce"),
2461 .value
= CFSTR("com.apple.account.Yahoo.password"),
2464 .attribute
= CFSTR("svce"),
2465 .value
= CFSTR("com.apple.account.Yahoo.oauth-token"),
2468 .attribute
= CFSTR("svce"),
2469 .value
= CFSTR("com.apple.account.Yahoo.oauth-token-nosync"),
2472 .attribute
= CFSTR("svce"),
2473 .value
= CFSTR("com.apple.account.Yahoo.oath-refresh-token"),
2476 .attribute
= CFSTR("svce"),
2477 .value
= CFSTR("com.apple.account.IMAPNotes.password"),
2480 .attribute
= CFSTR("svce"),
2481 .value
= CFSTR("com.apple.account.IMAPMail.password"),
2484 .attribute
= CFSTR("svce"),
2485 .value
= CFSTR("com.apple.account.126.password"),
2488 .attribute
= CFSTR("svce"),
2489 .value
= CFSTR("com.apple.account.163.password"),
2492 .attribute
= CFSTR("svce"),
2493 .value
= CFSTR("com.apple.account.aol.password"),
2498 ArrayContains(CFArrayRef array
, CFStringRef service
)
2500 return CFArrayContainsValue(array
, CFRangeMake(0, CFArrayGetCount(array
)), service
);
2504 _SecServerTransmogrifyToSyncBubble(CFArrayRef services
, uid_t uid
, SecurityClient
*client
, CFErrorRef
*error
)
2506 bool copyCloudAuthToken
= false;
2507 bool copyMobileMail
= false;
2509 bool copyPCS
= false;
2510 bool onlyDelete
= false;
2511 bool copyNSURLSesssion
= false;
2513 if (!client
->inMultiUser
)
2516 secnotice("syncbubble", "migration for uid %d uid for services %@", (int)uid
, services
);
2518 #if TARGET_OS_SIMULATOR
2521 if (uid
!= (uid_t
)client
->activeUser
)
2524 #error "no sync bubble on other platforms"
2528 * First select that services to copy/delete
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")))
2536 copyCloudAuthToken
= true;
2540 if (ArrayContains(services
, CFSTR("com.apple.nsurlsessiond.usermanager.sync")))
2542 copyCloudAuthToken
= true;
2543 copyNSURLSesssion
= true;
2546 if (ArrayContains(services
, CFSTR("com.apple.syncdefaultsd.usermanager.sync"))) {
2547 copyCloudAuthToken
= true;
2549 if (ArrayContains(services
, CFSTR("com.apple.mailq.sync")) || ArrayContains(services
, CFSTR("com.apple.mailq.sync.xpc"))) {
2550 copyCloudAuthToken
= true;
2551 copyMobileMail
= true;
2556 * The actually copy/delete the items selected
2559 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyPCS
, &inet_class
, PCSItems
, sizeof(PCSItems
)/sizeof(PCSItems
[0]), error
);
2561 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyPCS
, &genp_class
, PCSItems
, sizeof(PCSItems
)/sizeof(PCSItems
[0]), error
);
2565 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyMobileMail
, &genp_class
, MobileMailItems
, sizeof(MobileMailItems
)/sizeof(MobileMailItems
[0]), error
);
2569 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyCloudAuthToken
, &genp_class
, AccountsdItems
, sizeof(AccountsdItems
)/sizeof(AccountsdItems
[0]), error
);
2573 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyNSURLSesssion
, &inet_class
, NSURLSesssiond
, sizeof(NSURLSesssiond
)/sizeof(NSURLSesssiond
[0]), error
);
2581 * Migrate from user keychain to system keychain when switching to edu mode
2585 _SecServerTransmogrifyToSystemKeychain(SecurityClient
*client
, CFErrorRef
*error
)
2587 __block
bool ok
= true;
2590 * we are not in multi user yet, about to switch, otherwise we would
2591 * check that for client->inMultiuser here
2594 kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
2595 return kc_transaction(dbt
, error
, ^{
2596 CFDataRef systemUUID
= SecMUSRGetSystemKeychainUUID();
2598 const SecDbSchema
*newSchema
= kc_schemas
[0];
2599 SecDbClass
const *const *kcClass
;
2601 for (kcClass
= newSchema
->classes
; *kcClass
!= NULL
; kcClass
++) {
2602 CFErrorRef localError
= NULL
;
2605 if (*kcClass
== &tversion_class
|| *kcClass
== &identity_class
)
2608 q
= query_create(*kcClass
, SecMUSRGetSingleUserKeychainUUID(), NULL
, error
);
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.
2617 }, ^bool(CFMutableStringRef sql
, bool *needWhere
) {
2618 SecDbAppendWhereOrAnd(sql
, needWhere
);
2619 CFStringAppendFormat(sql
, NULL
, CFSTR("musr = ?"));
2621 }, ^bool(sqlite3_stmt
*stmt
, int col
) {
2622 return SecDbBindObject(stmt
, col
++, SecMUSRGetSingleUserKeychainUUID(), error
);
2623 }, ^(SecDbItemRef item
, bool *stop
) {
2624 CFErrorRef localError
= NULL
;
2626 if (!SecDbItemSetValueWithName(item
, kSecAttrMultiUser
, systemUUID
, &localError
)) {
2627 secerror("item: %@ update musr to system failed: %@", item
, localError
);
2632 if (!SecDbItemDoUpdate(item
, item
, dbt
, &localError
, ^bool (const SecDbAttr
*attr
) {
2633 return attr
->kind
== kSecDbRowIdAttr
;
2635 secerror("item: %@ insert during UPDATE: %@", item
, localError
);
2641 SecErrorPropagate(localError
, error
);
2642 CFReleaseSafe(localError
);
2646 query_destroy(q
, &localError
);
2657 * Delete account from local usage
2661 _SecServerDeleteMUSERViews(SecurityClient
*client
, uid_t uid
, CFErrorRef
*error
)
2663 return kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
2664 CFDataRef musrView
= NULL
, syncBubbleView
= NULL
;
2667 syncBubbleView
= SecMUSRCreateSyncBubbleUserUUID(uid
);
2668 require(syncBubbleView
, fail
);
2670 musrView
= SecMUSRCreateActiveUserUUID(uid
);
2671 require(musrView
, fail
);
2673 require(ok
= SecServerDeleteAllForUser(dbt
, syncBubbleView
, false, error
), fail
);
2674 require(ok
= SecServerDeleteAllForUser(dbt
, musrView
, false, error
), fail
);
2677 CFReleaseNull(syncBubbleView
);
2678 CFReleaseNull(musrView
);
2684 #endif /* TARGET_OS_IOS */
2687 _SecServerGetKeyStats(const SecDbClass
*qclass
,
2688 struct _SecServerKeyStats
*stats
)
2690 __block CFErrorRef error
= NULL
;
2693 Query
*q
= query_create(qclass
, NULL
, NULL
, &error
);
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
);
2707 kc_with_dbt(false, &error
, ^(SecDbConnectionRef dbconn
) {
2708 CFErrorRef error2
= NULL
;
2709 __block CFIndex totalSize
= 0;
2710 stats
->maxDataSize
= 0;
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
);
2718 CFIndex size
= CFDataGetLength(data
);
2719 if (size
> stats
->maxDataSize
)
2720 stats
->maxDataSize
= size
;
2724 CFReleaseNull(error3
);
2726 CFReleaseNull(error2
);
2728 stats
->averageSize
= totalSize
/ stats
->items
;
2737 CFReleaseNull(error
);
2739 query_destroy(q
, NULL
);