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>
43 #include <Security/SecTrustPriv.h>
44 #include <Security/SecTrustInternal.h>
45 #include <Security/SecCertificatePriv.h>
48 #include <MobileKeyBag/MobileKeyBag.h>
50 // TODO: Make this include work on both platforms. rdar://problem/16526848
51 #if TARGET_OS_EMBEDDED
52 #include <Security/SecEntitlements.h>
54 /* defines from <Security/SecEntitlements.h> */
55 #define kSecEntitlementAssociatedDomains CFSTR("com.apple.developer.associated-domains")
56 #define kSecEntitlementPrivateAssociatedDomains CFSTR("com.apple.private.associated-domains")
59 #if TARGET_OS_EMBEDDED
60 #include <AggregateDictionary/ADClient.h>
65 #include <utilities/array_size.h>
66 #include <utilities/SecFileLocations.h>
67 #include <utilities/SecTrace.h>
68 #include <utilities/SecXPCError.h>
69 #include <Security/SecuritydXPC.h>
70 #include "swcagent_client.h"
72 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
74 #include <SharedWebCredentials/SharedWebCredentials.h>
76 typedef OSStatus (*SWCCheckService_f
)(CFStringRef service
, CFStringRef appID
, CFStringRef domain
, SWCCheckServiceCompletion_b completion
);
77 typedef OSStatus (*SWCSetServiceFlags_f
)(CFStringRef service
, CFStringRef appID
, CFStringRef domain
, SWCFlags mask
, SWCFlags flags
, SWCSetServiceFlagsCompletion_b completion
);
79 typedef uint32_t SWCFlags
;
80 #define kSWCFlags_None 0
81 #define kSWCFlag_Pending ( 1U << 0 )
82 #define kSWCFlag_SiteApproved ( 1U << 1 )
83 #define kSWCFlag_SiteDenied ( 1U << 2 )
84 #define kSWCFlag_UserApproved ( 1U << 3 )
85 #define kSWCFlag_UserDenied ( 1U << 4 )
86 #define kSWCFlag_ExternalMask ( kSWCFlag_UserApproved | kSWCFlag_UserDenied )
89 /* Changed the name of the keychain changed notification, for testing */
90 static const char *g_keychain_changed_notification
= kSecServerKeychainChangedNotification
;
92 void SecItemServerSetKeychainChangedNotification(const char *notification_name
)
94 g_keychain_changed_notification
= notification_name
;
97 void SecKeychainChanged(bool syncWithPeers
) {
98 uint32_t result
= notify_post(g_keychain_changed_notification
);
100 SOSCCSyncWithAllPeers();
101 if (result
== NOTIFY_STATUS_OK
)
102 secnotice("item", "Sent %s%s", syncWithPeers
? "SyncWithAllPeers and " : "", g_keychain_changed_notification
);
104 secerror("%snotify_post %s returned: %" PRIu32
, syncWithPeers
? "Sent SyncWithAllPeers, " : "", g_keychain_changed_notification
, result
);
107 /* Return the current database version in *version. */
108 static bool SecKeychainDbGetVersion(SecDbConnectionRef dbt
, int *version
, CFErrorRef
*error
)
110 __block
bool ok
= true;
111 __block CFErrorRef localError
= NULL
;
112 __block
bool found
= false;
115 * First check for the version table itself
118 ok
&= SecDbPrepare(dbt
, CFSTR("SELECT name FROM sqlite_master WHERE type='table' AND name='tversion'"), &localError
, ^(sqlite3_stmt
*stmt
) {
119 ok
= SecDbStep(dbt
, stmt
, NULL
, ^(bool *stop
) {
124 require_action(ok
, out
, SecDbError(SQLITE_CORRUPT
, error
, CFSTR("Failed to read sqlite_master table: %@"), localError
));
126 secnotice("upgr", "no tversion table, will setup a new database: %@", localError
);
132 * Now build up major.minor
135 ok
&= SecDbPrepare(dbt
, CFSTR("SELECT version FROM tversion"), &localError
, ^(sqlite3_stmt
*stmt
) {
136 ok
= SecDbStep(dbt
, stmt
, NULL
, ^(bool *stop
) {
137 *version
= sqlite3_column_int(stmt
, 0);
142 if (ok
&& (*version
& 0xffff) >= 9) {
143 ok
&= SecDbPrepare(dbt
, CFSTR("SELECT minor FROM tversion WHERE version = ?"), &localError
, ^(sqlite3_stmt
*stmt
) {
144 ok
= SecDbBindInt(stmt
, 1, *version
, &localError
) &&
145 SecDbStep(dbt
, stmt
, NULL
, ^(bool *stop
) {
146 int64_t minor
= sqlite3_column_int64(stmt
, 0);
147 *version
|= ((minor
& 0xff) << 8) | ((minor
& 0xff0000) << 8);
154 secnotice("upgr", "database version is: 0x%08x : %d : %@", *version
, ok
, localError
);
155 CFReleaseSafe(localError
);
162 isClassD(SecDbItemRef item
)
164 CFTypeRef accessible
= SecDbItemGetCachedValueWithName(item
, kSecAttrAccessible
);
166 if (CFEqualSafe(accessible
, kSecAttrAccessibleAlways
) || CFEqualSafe(accessible
, kSecAttrAccessibleAlwaysThisDeviceOnly
))
171 #if TARGET_OS_EMBEDDED
174 measureDuration(struct timeval
*start
)
179 gettimeofday(&stop
, NULL
);
181 duration
= (stop
.tv_sec
-start
->tv_sec
) * 1000;
182 duration
+= (stop
.tv_usec
/ 1000) - (start
->tv_usec
/ 1000);
184 return SecBucket2Significant(duration
);
188 measureUpgradePhase1(struct timeval
*start
, bool success
, int64_t itemsMigrated
)
190 int64_t duration
= measureDuration(start
);
193 ADClientSetValueForScalarKey(CFSTR("com.apple.keychain.phase1.migrated-items-success"), itemsMigrated
);
194 ADClientSetValueForScalarKey(CFSTR("com.apple.keychain.phase1.migrated-time-success"), duration
);
196 ADClientSetValueForScalarKey(CFSTR("com.apple.keychain.phase1.migrated-items-fail"), itemsMigrated
);
197 ADClientSetValueForScalarKey(CFSTR("com.apple.keychain.phase1.migrated-time-fail"), duration
);
202 measureUpgradePhase2(struct timeval
*start
, int64_t itemsMigrated
)
204 int64_t duration
= measureDuration(start
);
206 ADClientSetValueForScalarKey(CFSTR("com.apple.keychain.phase2.migrated-items"), itemsMigrated
);
207 ADClientSetValueForScalarKey(CFSTR("com.apple.keychain.phase2.migrated-time"), duration
);
209 #endif /* TARGET_OS_EMBEDDED */
211 // Goes through all tables represented by old_schema and tries to migrate all items from them into new (current version) tables.
212 static bool UpgradeSchemaPhase1(SecDbConnectionRef dbt
, const SecDbSchema
*oldSchema
, CFErrorRef
*error
)
214 __block
bool ok
= true;
215 const SecDbSchema
*newSchema
= kc_schemas
[0];
216 SecDbClass
const *const *oldClass
;
217 SecDbClass
const *const *newClass
;
218 SecDbQueryRef query
= NULL
;
219 CFMutableStringRef sql
= NULL
;
220 #if TARGET_OS_EMBEDDED
221 __block
int64_t itemsMigrated
= 0;
222 struct timeval start
;
224 gettimeofday(&start
, NULL
);
227 // Rename existing tables to old names, as present in old schemas.
228 sql
= CFStringCreateMutable(NULL
, 0);
229 for (oldClass
= oldSchema
->classes
, newClass
= newSchema
->classes
;
230 *oldClass
!= NULL
&& *newClass
!= NULL
; oldClass
++, newClass
++) {
231 if (!CFEqual((*oldClass
)->name
, (*newClass
)->name
)) {
232 CFStringAppendFormat(sql
, NULL
, CFSTR("ALTER TABLE %@ RENAME TO %@;"),
233 (*newClass
)->name
, (*oldClass
)->name
);
235 CFStringAppendFormat(sql
, NULL
, CFSTR("DROP TABLE %@;"), (*oldClass
)->name
);
238 require_quiet(ok
&= SecDbExec(dbt
, sql
, error
), out
);
241 // Drop indices that that new schemas will use
242 sql
= CFStringCreateMutable(NULL
, 0);
243 for (newClass
= newSchema
->classes
; *newClass
!= NULL
; newClass
++) {
244 SecDbForEachAttrWithMask((*newClass
),desc
, kSecDbIndexFlag
| kSecDbInFlag
) {
245 CFStringAppendFormat(sql
, 0, CFSTR("DROP INDEX IF EXISTS %@%@;"), (*newClass
)->name
, desc
->name
);
248 require_quiet(ok
&= SecDbExec(dbt
, sql
, error
), out
);
251 // Create tables for new schema.
252 require_quiet(ok
&= SecItemDbCreateSchema(dbt
, newSchema
, false, error
), out
);
253 // Go through all classes of current schema to transfer all items to new tables.
254 for (oldClass
= oldSchema
->classes
, newClass
= newSchema
->classes
;
255 *oldClass
!= NULL
&& *newClass
!= NULL
; oldClass
++, newClass
++) {
256 if (CFEqual((*oldClass
)->name
, (*newClass
)->name
))
259 secnotice("upgr", "Upgrading table %@", (*oldClass
)->name
);
261 // Prepare query to iterate through all items in cur_class.
263 query_destroy(query
, NULL
);
264 require_quiet(query
= query_create(*oldClass
, SecMUSRGetAllViews(), NULL
, error
), out
);
266 ok
&= SecDbItemSelect(query
, dbt
, error
, ^bool(const SecDbAttr
*attr
) {
267 // We are interested in all attributes which are physically present in the DB.
268 return (attr
->flags
& kSecDbInFlag
) != 0;
269 }, ^bool(const SecDbAttr
*attr
) {
270 // No filtering please.
272 }, NULL
, NULL
, ^(SecDbItemRef item
, bool *stop
) {
273 CFErrorRef localError
= NULL
;
275 #if TARGET_OS_EMBEDDED
278 // Switch item to the new class.
279 item
->class = *newClass
;
281 if (isClassD(item
)) {
283 ok
&= SecDbItemEnsureDecrypted(item
, &localError
);
284 require_quiet(ok
, out
);
286 // Delete SHA1 field from the item, so that it is newly recalculated before storing
287 // the item into the new table.
288 require_quiet(ok
&= SecDbItemSetValue(item
, SecDbClassAttrWithKind(item
->class, kSecDbSHA1Attr
, error
),
289 kCFNull
, error
), out
);
291 // Leave item encrypted, do not ever try to decrypt it since it will fail.
292 item
->_edataState
= kSecDbItemAlwaysEncrypted
;
294 // Insert new item into the new table.
295 if (!SecDbItemInsert(item
, dbt
, &localError
)) {
296 secerror("item: %@ insert during upgrade: %@", item
, localError
);
302 OSStatus status
= SecErrorGetOSStatus(localError
);
305 // continue to upgrade and don't propagate errors for insert failures
306 // that are typical of a single item failure
308 case errSecDuplicateItem
:
311 case errSecInteractionNotAllowed
:
312 case errSecAuthNeeded
:
316 ok
&= CFErrorPropagate(CFRetainSafe(localError
), error
);
319 CFReleaseSafe(localError
);
324 require_quiet(ok
, out
);
327 // Remove old tables from the DB.
328 sql
= CFStringCreateMutable(NULL
, 0);
329 for (oldClass
= oldSchema
->classes
, newClass
= newSchema
->classes
;
330 *oldClass
!= NULL
&& *newClass
!= NULL
; oldClass
++, newClass
++) {
331 if (!CFEqual((*oldClass
)->name
, (*newClass
)->name
)) {
332 CFStringAppendFormat(sql
, NULL
, CFSTR("DROP TABLE %@;"), (*oldClass
)->name
);
335 require_quiet(ok
&= SecDbExec(dbt
, sql
, error
), out
);
338 #if TARGET_OS_EMBEDDED
339 measureUpgradePhase1(&start
, ok
, SecBucket2Significant(itemsMigrated
));
343 query_destroy(query
, NULL
);
349 // Goes through all tables represented by old_schema and tries to migrate all items from them into new (current version) tables.
350 static bool UpgradeItemPhase2(SecDbConnectionRef dbt
, bool *inProgress
, CFErrorRef
*error
) {
351 __block
bool ok
= true;
352 SecDbQueryRef query
= NULL
;
353 #if TARGET_OS_EMBEDDED
354 __block
int64_t itemsMigrated
= 0;
355 struct timeval start
;
357 gettimeofday(&start
, NULL
);
360 // Go through all classes in new schema
361 const SecDbSchema
*newSchema
= kc_schemas
[0];
362 for (const SecDbClass
*const *class = newSchema
->classes
; *class != NULL
&& !*inProgress
; class++) {
363 if(CFEqual((*class)->name
, tversion_class
.name
)) {
364 //Don't try to decrypt items in tversion table
368 const SecDbAttr
*pdmn
= SecDbClassAttrWithKind(*class, kSecDbAccessAttr
, error
);
373 // Prepare query to go through all non-DK|DKU items
375 query_destroy(query
, NULL
);
377 require_action_quiet(query
= query_create(*class, SecMUSRGetAllViews(), NULL
, error
), out
, ok
= false);
378 ok
= SecDbItemSelect(query
, dbt
, error
, NULL
, ^bool(const SecDbAttr
*attr
) {
379 // No simple per-attribute filtering.
381 }, ^bool(CFMutableStringRef sql
, bool *needWhere
) {
382 // Select only non-D-class items
383 SecDbAppendWhereOrAnd(sql
, needWhere
);
384 CFStringAppendFormat(sql
, NULL
, CFSTR("NOT %@ IN (?,?)"), pdmn
->name
);
386 }, ^bool(sqlite3_stmt
*stmt
, int col
) {
387 return SecDbBindObject(stmt
, col
++, kSecAttrAccessibleAlwaysPrivate
, error
) &&
388 SecDbBindObject(stmt
, col
++, kSecAttrAccessibleAlwaysThisDeviceOnlyPrivate
, error
);
389 }, ^(SecDbItemRef item
, bool *stop
) {
390 CFErrorRef localError
= NULL
;
392 #if TARGET_OS_EMBEDDED
397 if (SecDbItemEnsureDecrypted(item
, &localError
)) {
399 // Delete SHA1 field from the item, so that it is newly recalculated before storing
400 // the item into the new table.
401 require_quiet(ok
= SecDbItemSetValue(item
, SecDbClassAttrWithKind(item
->class, kSecDbSHA1Attr
, error
),
402 kCFNull
, &localError
), out
);
404 // Replace item with the new value in the table; this will cause the item to be decoded and recoded back,
405 // incl. recalculation of item's hash.
406 ok
= SecDbItemUpdate(item
, item
, dbt
, false, &localError
);
410 CFIndex status
= CFErrorGetCode(localError
);
414 // Items producing errSecDecode are silently dropped - they are not decodable and lost forever.
415 (void)SecDbItemDelete(item
, dbt
, false, error
);
418 case errSecInteractionNotAllowed
:
419 // If we are still not able to decrypt the item because the class key is not released yet,
420 // remember that DB still needs phase2 migration to be run next time a connection is made. Also
421 // stop iterating next items, it would be just waste of time because the whole iteration will be run
422 // next time when this phase2 will be rerun.
427 case errSecAuthNeeded
:
428 // errSecAuthNeeded means that it is an ACL-based item which requires authentication (or at least
429 // ACM context, which we do not have).
433 // Other errors should abort the migration completely.
434 ok
= CFErrorPropagate(CFRetainSafe(localError
), error
);
440 CFReleaseSafe(localError
);
441 *stop
= *stop
|| !ok
;
447 #if TARGET_OS_EMBEDDED
448 measureUpgradePhase2(&start
, SecBucket2Significant(itemsMigrated
));
453 query_destroy(query
, NULL
);
457 #define SCHEMA_VERSION(schema) ((((schema)->minorVersion) << 8) | ((schema)->majorVersion))
458 #define VERSION_MAJOR(version) ((version) & 0xff)
459 #define VERSION_MINOR(version) (((version) >> 8) & 0xff)
460 #define VERSION_NEW(version) ((version) & 0xffff)
461 #define VERSION_OLD(version) (((version) >> 16) & 0xffff)
463 static bool SecKeychainDbUpgradeFromVersion(SecDbConnectionRef dbt
, int version
, bool *inProgress
, CFErrorRef
*error
) {
464 __block
bool didPhase2
= false;
465 __block
bool ok
= true;
466 __block CFErrorRef localError
= NULL
;
471 // The schema we want to have is the first in the list of schemas.
472 const SecDbSchema
*newSchema
= kc_schemas
[0];
474 // If DB schema is the one we want, we are done.
475 require_quiet(SCHEMA_VERSION(newSchema
) != version
, out
);
477 // Check if the schema of the database on disk is the same major, but newer version then what we have
478 // in code, lets just skip this since a newer version of the OS have upgrade it. Since its the same
479 // major, its a promise that it will be compatible.
480 if (newSchema
->majorVersion
== VERSION_MAJOR(version
) && newSchema
->minorVersion
< VERSION_MINOR(version
)) {
481 secnotice("upgr", "skipping upgrade since minor is newer");
485 if (VERSION_MAJOR(version
) < 6) {
486 // Pre v6 keychains need to have WAL enabled, since SecDb only does this at db creation time.
487 // NOTE: This has to be run outside of a transaction.
488 require_action_quiet(ok
= (SecDbExec(dbt
, CFSTR("PRAGMA auto_vacuum = FULL"), &localError
) &&
489 SecDbExec(dbt
, CFSTR("PRAGMA journal_mode = WAL"), &localError
)),
490 out
, secerror("unable to enable WAL or auto vacuum, marking DB as corrupt: %@",
494 ok
&= SecDbTransaction(dbt
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
495 CFStringRef sql
= NULL
;
496 bool didPhase1
= false;
498 // Get version again once we start a transaction, someone else might change the migration state.
500 require_quiet(ok
= SecKeychainDbGetVersion(dbt
, &version2
, &localError
), out
);
501 // Check if someone has raced us to the migration of the database
502 require_action(version
== version2
, out
, CFReleaseNull(localError
); ok
= true);
504 require_quiet(SCHEMA_VERSION(newSchema
) != version2
, out
);
506 // If this is empty database, just create table according to schema and be done with it.
507 require_action_quiet(version2
!= 0, out
, ok
= SecItemDbCreateSchema(dbt
, newSchema
, true, &localError
));
509 int oldVersion
= VERSION_OLD(version2
);
510 version2
= VERSION_NEW(version2
);
512 require_action_quiet(version2
== SCHEMA_VERSION(newSchema
) || oldVersion
== 0, out
,
513 ok
= SecDbError(SQLITE_CORRUPT
, &localError
,
514 CFSTR("Half migrated but obsolete DB found: found 0x%x(0x%x) but 0x%x is needed"),
515 version2
, oldVersion
, SCHEMA_VERSION(newSchema
)));
517 // Check whether we have both old and new tables in the DB.
518 if (oldVersion
== 0) {
519 // Pure old-schema migration attempt, with full blown table renames etc (a.k.a. phase1)
520 oldVersion
= version2
;
521 version2
= SCHEMA_VERSION(newSchema
);
523 // Find schema for old database.
524 const SecDbSchema
*oldSchema
= NULL
;
525 for (const SecDbSchema
* const *pschema
= kc_schemas
; *pschema
; ++pschema
) {
526 if (SCHEMA_VERSION((*pschema
)) == oldVersion
) {
527 oldSchema
= *pschema
;
532 // If we are attempting to upgrade from a version for which we have no schema, fail.
533 require_action_quiet(oldSchema
!= NULL
, out
,
534 ok
= SecDbError(SQLITE_CORRUPT
, &localError
, CFSTR("no schema for version: 0x%x"), oldVersion
);
535 secerror("no schema for version 0x%x", oldVersion
));
537 secnotice("upgr", "Upgrading from version 0x%x to 0x%x", oldVersion
, SCHEMA_VERSION(newSchema
));
538 require(ok
= UpgradeSchemaPhase1(dbt
, oldSchema
, &localError
), out
);
544 CFErrorRef phase2Error
= NULL
;
546 // Lests try to go through non-D-class items in new tables and apply decode/encode on them
547 // If this fails the error will be ignored after doing a phase1 since but not in the second
548 // time when we are doing phase2.
549 ok
= UpgradeItemPhase2(dbt
, inProgress
, &phase2Error
);
555 SecErrorPropagate(phase2Error
, &localError
);
558 CFReleaseNull(phase2Error
);
562 // If either migration path we did reported that the migration was complete, signalize that
563 // in the version database by cleaning oldVersion (which is stored in upper halfword of the version)
564 secnotice("upgr", "Done upgrading from version 0x%x to 0x%x", oldVersion
, SCHEMA_VERSION(newSchema
));
571 // Update database version table.
572 uint32_t major
= (VERSION_MAJOR(version2
)) | (VERSION_MAJOR(oldVersion
) << 16);
573 uint32_t minor
= (VERSION_MINOR(version2
)) | (VERSION_MINOR(oldVersion
) << 16);
574 secnotice("upgr", "Upgrading saving version major 0x%x minor 0x%x", major
, minor
);
575 sql
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("UPDATE tversion SET version='%d', minor='%d'"),
577 require_quiet(ok
= SecDbExec(dbt
, sql
, &localError
), out
);
584 if (ok
&& didPhase2
) {
585 #if TARGET_OS_EMBEDDED
586 ADClientAddValueForScalarKey(CFSTR("com.apple.keychain.migration-success"), 1);
591 if (!ok
|| localError
) {
593 * We assume that database is corrupt at this point, but we need to
594 * check if the error we got isn't severe enough to mark the database as corrupt.
595 * In those cases we opt out of corrupting the database.
597 bool markedCorrupt
= true;
600 secwarning("upgrade: error has been set but status is true");
604 CFStringRef domain
= CFErrorGetDomain(localError
);
605 CFIndex code
= CFErrorGetCode(localError
);
607 if (CFEqualSafe(domain
, kSecDbErrorDomain
) &&
608 ((code
& 0xff) == SQLITE_LOCKED
|| (code
& 0xff) == SQLITE_BUSY
))
610 /* sqlite just busy doing something else, lets try upgrading some other time */
612 markedCorrupt
= false;
613 CFReleaseNull(localError
);
615 secerror("unable to complete upgrade, marking DB as corrupt: %@", localError
);
618 secerror("unable to complete upgrade and no error object returned, marking DB as corrupt");
621 SecDbCorrupt(dbt
, localError
);
622 #if TARGET_OS_EMBEDDED
623 ADClientAddValueForScalarKey(CFSTR("com.apple.keychain.migration-failure"), 1);
629 *error
= (CFErrorRef
)CFRetain(localError
);
631 CFReleaseNull(localError
);
637 /* AUDIT[securityd](done):
638 accessGroup (ok) is a caller provided, non NULL CFTypeRef.
640 Return true iff accessGroup is allowable according to accessGroups.
642 static bool accessGroupsAllows(CFArrayRef accessGroups
,
643 CFStringRef accessGroup
) {
644 /* NULL accessGroups is wildcard. */
647 /* Make sure we have a string. */
648 if (!isString(accessGroup
))
651 /* Having the special accessGroup "*" allows access to all accessGroups. */
652 CFRange range
= { 0, CFArrayGetCount(accessGroups
) };
654 (CFArrayContainsValue(accessGroups
, range
, accessGroup
) ||
655 CFArrayContainsValue(accessGroups
, range
, CFSTR("*"))))
661 bool itemInAccessGroup(CFDictionaryRef item
, CFArrayRef accessGroups
) {
662 return accessGroupsAllows(accessGroups
,
663 CFDictionaryGetValue(item
, kSecAttrAccessGroup
));
667 static CF_RETURNS_RETAINED CFDataRef
SecServerExportBackupableKeychain(SecDbConnectionRef dbt
,
668 SecurityClient
*client
,
669 keybag_handle_t src_keybag
, keybag_handle_t dest_keybag
, CFErrorRef
*error
) {
670 CFDataRef data_out
= NULL
;
671 /* Export everything except the items for which SecItemIsSystemBound()
673 CFDictionaryRef keychain
= SecServerCopyKeychainPlist(dbt
, client
,
674 src_keybag
, dest_keybag
, kSecBackupableItemFilter
,
677 data_out
= CFPropertyListCreateData(kCFAllocatorDefault
, keychain
,
678 kCFPropertyListBinaryFormat_v1_0
,
686 static bool SecServerImportBackupableKeychain(SecDbConnectionRef dbt
,
687 SecurityClient
*client
,
688 keybag_handle_t src_keybag
,
689 keybag_handle_t dest_keybag
,
693 return kc_transaction(dbt
, error
, ^{
695 CFDictionaryRef keychain
;
696 keychain
= CFPropertyListCreateWithData(kCFAllocatorDefault
, data
,
697 kCFPropertyListImmutable
, NULL
,
700 if (isDictionary(keychain
)) {
701 ok
= SecServerImportKeychainInPlist(dbt
,
706 kSecBackupableItemFilter
,
709 ok
= SecError(errSecParam
, error
, CFSTR("import: keychain is not a dictionary"));
719 * Similar to ks_open_keybag, but goes through MKB interface
721 static bool mkb_open_keybag(CFDataRef keybag
, CFDataRef password
, MKBKeyBagHandleRef
*handle
, CFErrorRef
*error
) {
723 MKBKeyBagHandleRef mkbhandle
= NULL
;
725 rc
= MKBKeyBagCreateWithData(keybag
, &mkbhandle
);
726 if (rc
!= kMobileKeyBagSuccess
) {
727 return SecKernError(rc
, error
, CFSTR("MKBKeyBagCreateWithData failed: %d"), rc
);
731 rc
= MKBKeyBagUnlock(mkbhandle
, password
);
732 if (rc
!= kMobileKeyBagSuccess
) {
733 CFRelease(mkbhandle
);
734 return SecKernError(rc
, error
, CFSTR("failed to unlock bag: %d"), rc
);
745 static CFDataRef
SecServerKeychainCreateBackup(SecDbConnectionRef dbt
, SecurityClient
*client
, CFDataRef keybag
,
746 CFDataRef password
, CFErrorRef
*error
) {
747 CFDataRef backup
= NULL
;
748 keybag_handle_t backup_keybag
;
750 MKBKeyBagHandleRef mkbhandle
= NULL
;
751 require(mkb_open_keybag(keybag
, password
, &mkbhandle
, error
), out
);
753 require_noerr(MKBKeyBagGetAKSHandle(mkbhandle
, &backup_keybag
), out
);
756 backup_keybag
= KEYBAG_NONE
;
758 /* Export from system keybag to backup keybag. */
759 backup
= SecServerExportBackupableKeychain(dbt
, client
, KEYBAG_DEVICE
, backup_keybag
, error
);
764 CFRelease(mkbhandle
);
769 static bool SecServerKeychainRestore(SecDbConnectionRef dbt
,
770 SecurityClient
*client
,
777 keybag_handle_t backup_keybag
;
779 MKBKeyBagHandleRef mkbhandle
= NULL
;
780 require(mkb_open_keybag(keybag
, password
, &mkbhandle
, error
), out
);
782 require_noerr(MKBKeyBagGetAKSHandle(mkbhandle
, &backup_keybag
), out
);
784 backup_keybag
= KEYBAG_NONE
;
786 /* Import from backup keybag to system keybag. */
787 require(SecServerImportBackupableKeychain(dbt
, client
, backup_keybag
, KEYBAG_DEVICE
, backup
, error
), out
);
793 CFRelease(mkbhandle
);
799 // MARK - External SPI support code.
801 CFStringRef
__SecKeychainCopyPath(void) {
802 CFStringRef kcRelPath
= NULL
;
804 kcRelPath
= CFSTR("keychain-2.db");
806 kcRelPath
= CFSTR("keychain-2-debug.db");
809 CFStringRef kcPath
= NULL
;
810 CFURLRef kcURL
= SecCopyURLForFileInKeychainDirectory(kcRelPath
);
812 kcPath
= CFURLCopyFileSystemPath(kcURL
, kCFURLPOSIXPathStyle
);
819 // MARK: kc_dbhandle init and reset
821 SecDbRef
SecKeychainDbCreate(CFStringRef path
) {
822 return SecDbCreate(path
, ^bool (SecDbConnectionRef dbconn
, bool didCreate
, bool *callMeAgainForNextConnection
, CFErrorRef
*error
) {
823 // Upgrade from version 0 means create the schema in empty db.
827 ok
= SecKeychainDbGetVersion(dbconn
, &version
, error
);
829 ok
= ok
&& SecKeychainDbUpgradeFromVersion(dbconn
, version
, callMeAgainForNextConnection
, error
);
831 secerror("Upgrade %sfailed: %@", didCreate
? "from v0 " : "", error
? *error
: NULL
);
837 static SecDbRef _kc_dbhandle
= NULL
;
839 static void kc_dbhandle_init(void) {
840 SecDbRef oldHandle
= _kc_dbhandle
;
842 CFStringRef dbPath
= __SecKeychainCopyPath();
844 _kc_dbhandle
= SecKeychainDbCreate(dbPath
);
847 secerror("no keychain path available");
850 secerror("replaced %@ with %@", oldHandle
, _kc_dbhandle
);
851 CFRelease(oldHandle
);
855 // A callback for the sqlite3_log() interface.
856 static void sqlite3Log(void *pArg
, int iErrCode
, const char *zMsg
){
857 secdebug("sqlite3", "(%d) %s", iErrCode
, zMsg
);
861 _SecServerDatabaseSetup(void)
863 static dispatch_once_t onceToken
;
864 dispatch_once(&onceToken
, ^{
865 int rx
= sqlite3_config(SQLITE_CONFIG_LOG
, sqlite3Log
, NULL
);
866 if (SQLITE_OK
!= rx
) {
867 secwarning("Could not set up sqlite global error logging to syslog: %d", rx
);
872 static SecDbRef
kc_dbhandle(void)
874 static dispatch_once_t onceToken
;
875 dispatch_once(&onceToken
, ^{
876 _SecServerDatabaseSetup();
882 /* For whitebox testing only */
883 void SecKeychainDbReset(dispatch_block_t inbetween
)
885 CFStringRef dbPath
= __SecKeychainCopyPath();
889 CFReleaseNull(_kc_dbhandle
);
894 _kc_dbhandle
= SecKeychainDbCreate(dbPath
);
898 static SecDbConnectionRef
kc_aquire_dbt(bool writeAndRead
, CFErrorRef
*error
) {
899 SecDbRef db
= kc_dbhandle();
901 SecError(errSecDataNotAvailable
, error
, CFSTR("failed to get a db handle"));
904 return SecDbConnectionAquire(db
, !writeAndRead
, error
);
907 /* Return a per thread dbt handle for the keychain. If create is true create
908 the database if it does not yet exist. If it is false, just return an
909 error if it fails to auto-create. */
910 static bool kc_with_dbt(bool writeAndRead
, CFErrorRef
*error
, bool (^perform
)(SecDbConnectionRef dbt
))
912 // Make sure we initialize our engines before writing to the keychain
914 SecItemDataSourceFactoryGetDefault();
917 SecDbConnectionRef dbt
= kc_aquire_dbt(writeAndRead
, error
);
920 SecDbConnectionRelease(dbt
);
926 items_matching_issuer_parent(SecDbConnectionRef dbt
, CFArrayRef accessGroups
, CFDataRef musrView
,
927 CFDataRef issuer
, CFArrayRef issuers
, int recurse
)
930 CFArrayRef results
= NULL
;
934 if (CFArrayContainsValue(issuers
, CFRangeMake(0, CFArrayGetCount(issuers
)), issuer
))
937 /* XXX make musr supported */
938 const void *keys
[] = { kSecClass
, kSecReturnRef
, kSecAttrSubject
};
939 const void *vals
[] = { kSecClassCertificate
, kCFBooleanTrue
, issuer
};
940 CFDictionaryRef query
= CFDictionaryCreate(kCFAllocatorDefault
, keys
, vals
, array_size(keys
), NULL
, NULL
);
945 CFErrorRef localError
= NULL
;
946 q
= query_create_with_limit(query
, musrView
, kSecMatchUnlimited
, &localError
);
949 s3dl_copy_matching(dbt
, q
, (CFTypeRef
*)&results
, accessGroups
, &localError
);
950 query_destroy(q
, &localError
);
953 secerror("items matching issuer parent: %@", localError
);
954 CFReleaseNull(localError
);
958 count
= CFArrayGetCount(results
);
959 for (i
= 0; (i
< count
) && !found
; i
++) {
960 CFDictionaryRef cert_dict
= (CFDictionaryRef
)CFArrayGetValueAtIndex(results
, i
);
961 CFDataRef cert_issuer
= CFDictionaryGetValue(cert_dict
, kSecAttrIssuer
);
962 if (CFEqual(cert_issuer
, issuer
))
965 found
= items_matching_issuer_parent(dbt
, accessGroups
, musrView
, cert_issuer
, issuers
, recurse
);
967 CFReleaseSafe(results
);
973 _FilterWithPolicy(SecPolicyRef policy
, CFDateRef date
, SecCertificateRef cert
)
975 CFDictionaryRef props
= NULL
;
976 CFArrayRef keychains
= NULL
;
977 CFArrayRef anchors
= NULL
;
978 CFArrayRef certs
= NULL
;
979 CFArrayRef chain
= NULL
;
980 SecTrustRef trust
= NULL
;
982 SecTrustResultType trustResult
;
983 Boolean needChain
= false;
984 __block
bool ok
= false;
986 if (!policy
|| !cert
) return false;
988 certs
= CFArrayCreate(NULL
, (const void **)&cert
, (CFIndex
)1, &kCFTypeArrayCallBacks
);
989 require_noerr_quiet(SecTrustCreateWithCertificates(certs
, policy
, &trust
), cleanup
);
991 /* Set evaluation date, if specified (otherwise current date is implied) */
992 if (date
&& (CFGetTypeID(date
) == CFDateGetTypeID())) {
993 require_noerr_quiet(SecTrustSetVerifyDate(trust
, date
), cleanup
);
996 /* Check whether this is the X509 Basic policy, which means chain building */
997 props
= SecPolicyCopyProperties(policy
);
999 CFTypeRef oid
= (CFTypeRef
) CFDictionaryGetValue(props
, kSecPolicyOid
);
1000 if (oid
&& (CFEqual(oid
, kSecPolicyAppleX509Basic
) ||
1001 CFEqual(oid
, kSecPolicyAppleRevocation
))) {
1007 require_noerr_quiet(SecTrustEvaluateLeafOnly(trust
, &trustResult
), cleanup
);
1009 require_noerr_quiet(SecTrustEvaluate(trust
, &trustResult
), cleanup
);
1012 require_quiet((trustResult
== kSecTrustResultProceed
||
1013 trustResult
== kSecTrustResultUnspecified
||
1014 trustResult
== kSecTrustResultRecoverableTrustFailure
), cleanup
);
1017 #if TARGET_OS_IPHONE
1018 CFArrayRef properties
= SecTrustCopyProperties(trust
);
1020 CFArrayRef properties
= SecTrustCopyProperties_ios(trust
);
1023 CFArrayForEach(properties
, ^(const void *property
) {
1024 CFDictionaryForEach((CFDictionaryRef
)property
, ^(const void *key
, const void *value
) {
1025 if (CFEqual((CFTypeRef
)key
, kSecPropertyKeyType
) && CFEqual((CFTypeRef
)value
, kSecPropertyTypeError
))
1029 CFRelease(properties
);
1033 if(props
) CFRelease(props
);
1034 if(chain
) CFRelease(chain
);
1035 if(anchors
) CFRelease(anchors
);
1036 if(keychains
) CFRelease(keychains
);
1037 if(certs
) CFRelease(certs
);
1038 if(trust
) CFRelease(trust
);
1044 _FilterWithDate(CFDateRef validOnDate
, SecCertificateRef cert
)
1046 if (!validOnDate
|| !cert
) return false;
1048 CFAbsoluteTime at
, nb
, na
;
1049 at
= CFDateGetAbsoluteTime((CFDateRef
)validOnDate
);
1052 nb
= SecCertificateNotValidBefore(cert
);
1053 na
= SecCertificateNotValidAfter(cert
);
1055 if (nb
== 0 || na
== 0 || nb
== na
) {
1057 secnotice("FilterWithDate", "certificate cannot operate");
1061 secnotice("FilterWithDate", "certificate is not valid yet");
1065 secnotice("FilterWithDate", "certificate expired");
1072 _FilterWithTrust(Boolean trustedOnly
, SecCertificateRef cert
)
1074 if (!cert
) return false;
1075 if (!trustedOnly
) return true;
1078 CFArrayRef certArray
= CFArrayCreate(NULL
, (const void**)&cert
, 1, &kCFTypeArrayCallBacks
);
1079 SecTrustRef trust
= NULL
;
1080 SecPolicyRef policy
= SecPolicyCreateBasicX509();
1081 require_quiet(policy
, out
);
1083 require_noerr_quiet(SecTrustCreateWithCertificates(certArray
, policy
, &trust
), out
);
1084 SecTrustResultType trustResult
;
1085 require_noerr_quiet(SecTrustEvaluate(trust
, &trustResult
), out
);
1087 require_quiet((trustResult
== kSecTrustResultProceed
||
1088 trustResult
== kSecTrustResultUnspecified
), out
);
1091 CFReleaseSafe(trust
);
1092 CFReleaseSafe(policy
);
1093 CFReleaseSafe(certArray
);
1097 static SecCertificateRef
1098 CopyCertificateFromItem(Query
*q
, CFDictionaryRef item
) {
1099 SecCertificateRef certRef
= NULL
;
1101 CFTypeRef tokenID
= NULL
;
1102 CFDataRef certData
= NULL
;
1103 if (q
->q_class
== &identity_class
) {
1104 certData
= CFDictionaryGetValue(item
, kSecAttrIdentityCertificateData
);
1105 tokenID
= CFDictionaryGetValue(item
, kSecAttrIdentityCertificateTokenID
);
1106 } else if (q
->q_class
== &cert_class
) {
1107 certData
= CFDictionaryGetValue(item
, kSecValueData
);
1108 tokenID
= CFDictionaryGetValue(item
, kSecAttrTokenID
);
1111 require_quiet(certData
, out
);
1112 if (tokenID
!= NULL
) {
1113 CFErrorRef error
= NULL
;
1114 CFDataRef tokenCertData
= _SecTokenItemCopyValueData(certData
, &error
);
1115 require_action_quiet(tokenCertData
, out
, { secerror("function _SecTokenItemCopyValueData failed with: %@", error
); CFReleaseSafe(error
); });
1116 certRef
= SecCertificateCreateWithData(kCFAllocatorDefault
, tokenCertData
);
1117 CFRelease(tokenCertData
);
1120 certRef
= SecCertificateCreateWithData(kCFAllocatorDefault
, certData
);
1126 bool match_item(SecDbConnectionRef dbt
, Query
*q
, CFArrayRef accessGroups
, CFDictionaryRef item
)
1129 SecCertificateRef certRef
= NULL
;
1130 if (q
->q_match_issuer
) {
1131 CFDataRef issuer
= CFDictionaryGetValue(item
, kSecAttrIssuer
);
1132 if (!items_matching_issuer_parent(dbt
, accessGroups
, q
->q_musrView
, issuer
, q
->q_match_issuer
, 10 /*max depth*/))
1136 if (q
->q_match_policy
&& (q
->q_class
== &identity_class
|| q
->q_class
== &cert_class
)) {
1138 certRef
= CopyCertificateFromItem(q
, item
);
1139 require_quiet(certRef
, out
);
1140 require_quiet(_FilterWithPolicy(q
->q_match_policy
, q
->q_match_valid_on_date
, certRef
), out
);
1143 if (q
->q_match_valid_on_date
&& (q
->q_class
== &identity_class
|| q
->q_class
== &cert_class
)) {
1145 certRef
= CopyCertificateFromItem(q
, item
);
1146 require_quiet(certRef
, out
);
1147 require_quiet(_FilterWithDate(q
->q_match_valid_on_date
, certRef
), out
);
1150 if (q
->q_match_trusted_only
&& (q
->q_class
== &identity_class
|| q
->q_class
== &cert_class
)) {
1152 certRef
= CopyCertificateFromItem(q
, item
);
1153 require_quiet(certRef
, out
);
1154 require_quiet(_FilterWithTrust(CFBooleanGetValue(q
->q_match_trusted_only
), certRef
), out
);
1157 /* Add future match checks here. */
1160 CFReleaseSafe(certRef
);
1164 /****************************************************************************
1165 **************** Beginning of Externally Callable Interface ****************
1166 ****************************************************************************/
1168 void (*SecTaskDiagnoseEntitlements
)(CFArrayRef accessGroups
) = NULL
;
1170 /* AUDIT[securityd](done):
1171 query (ok) is a caller provided dictionary, only its cf type has been checked.
1174 SecItemServerCopyMatching(CFDictionaryRef query
, CFTypeRef
*result
,
1175 SecurityClient
*client
, CFErrorRef
*error
)
1177 CFArrayRef accessGroups
= client
->accessGroups
;
1180 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
))) {
1181 if (SecTaskDiagnoseEntitlements
)
1182 SecTaskDiagnoseEntitlements(accessGroups
);
1183 return SecError(errSecMissingEntitlement
, error
,
1184 CFSTR("client has neither application-identifier nor keychain-access-groups entitlements"));
1187 if (CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), CFSTR("*"))) {
1188 /* Having the special accessGroup "*" allows access to all accessGroups. */
1189 accessGroups
= NULL
;
1193 Query
*q
= query_create_with_limit(query
, client
->musr
, 1, error
);
1195 CFStringRef agrp
= CFDictionaryGetValue(q
->q_item
, kSecAttrAccessGroup
);
1196 if (agrp
&& accessGroupsAllows(accessGroups
, agrp
)) {
1197 // TODO: Return an error if agrp is not NULL and accessGroupsAllows() fails above.
1198 const void *val
= agrp
;
1199 accessGroups
= CFArrayCreate(0, &val
, 1, &kCFTypeArrayCallBacks
);
1201 CFRetainSafe(accessGroups
);
1204 #if TARGET_OS_IPHONE
1205 if (q
->q_sync_bubble
&& client
->inMultiUser
) {
1206 CFReleaseNull(q
->q_musrView
);
1207 q
->q_musrView
= SecMUSRCreateSyncBubbleUserUUID(q
->q_sync_bubble
);
1208 } else if (client
->inMultiUser
&& client
->isNetworkExtension
) {
1209 CFReleaseNull(q
->q_musrView
);
1210 q
->q_musrView
= SecMUSRCreateBothUserAndSystemUUID(client
->uid
);
1211 } else if (q
->q_system_keychain
&& client
->inMultiUser
) {
1212 CFReleaseNull(q
->q_musrView
);
1213 q
->q_musrView
= SecMUSRCopySystemKeychainUUID();
1215 q
->q_system_keychain
= false;
1219 query_set_caller_access_groups(q
, accessGroups
);
1221 /* Sanity check the query. */
1222 if (q
->q_system_keychain
&& !client
->allowSystemKeychain
) {
1223 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for system keychain"));
1224 } else if (q
->q_sync_bubble
&& !client
->allowSyncBubbleKeychain
) {
1225 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for syncbubble keychain"));
1226 } else if (q
->q_system_keychain
&& q
->q_sync_bubble
) {
1227 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("can't do both system and syncbubble keychain"));
1228 } else if (q
->q_use_item_list
) {
1229 ok
= SecError(errSecUseItemListUnsupported
, error
, CFSTR("use item list unsupported"));
1230 } else if (q
->q_match_issuer
&& ((q
->q_class
!= &cert_class
) &&
1231 (q
->q_class
!= &identity_class
))) {
1232 ok
= SecError(errSecUnsupportedOperation
, error
, CFSTR("unsupported match attribute"));
1233 } else if (q
->q_match_policy
&& ((q
->q_class
!= &cert_class
) &&
1234 (q
->q_class
!= &identity_class
))) {
1235 ok
= SecError(errSecUnsupportedOperation
, error
, CFSTR("unsupported kSecMatchPolicy attribute"));
1236 } else if (q
->q_return_type
!= 0 && result
== NULL
) {
1237 ok
= SecError(errSecReturnMissingPointer
, error
, CFSTR("missing pointer"));
1238 } else if (!q
->q_error
) {
1239 ok
= kc_with_dbt(false, error
, ^(SecDbConnectionRef dbt
) {
1240 return s3dl_copy_matching(dbt
, q
, result
, accessGroups
, error
);
1244 CFReleaseSafe(accessGroups
);
1245 if (!query_destroy(q
, error
))
1253 _SecItemCopyMatching(CFDictionaryRef query
, SecurityClient
*client
, CFTypeRef
*result
, CFErrorRef
*error
) {
1254 return SecItemServerCopyMatching(query
, result
, client
, error
);
1257 #if TARGET_OS_IPHONE
1259 SecItemSynchronizable(CFDictionaryRef query
)
1261 bool result
= false;
1262 CFTypeRef value
= CFDictionaryGetValue(query
, kSecAttrSynchronizable
);
1263 if (isBoolean(value
))
1264 return CFBooleanGetValue(value
);
1265 else if (isNumber(value
)) {
1267 (void)CFNumberGetValue(value
, kCFNumberSInt32Type
, &number
);
1276 /* AUDIT[securityd](done):
1277 attributes (ok) is a caller provided dictionary, only its cf type has
1281 _SecItemAdd(CFDictionaryRef attributes
, SecurityClient
*client
, CFTypeRef
*result
, CFErrorRef
*error
)
1283 CFArrayRef accessGroups
= client
->accessGroups
;
1287 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
)) ||
1288 (ag_count
== 1 && CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), kSecAttrAccessGroupToken
))) {
1289 if (SecTaskDiagnoseEntitlements
)
1290 SecTaskDiagnoseEntitlements(accessGroups
);
1291 return SecError(errSecMissingEntitlement
, error
,
1292 CFSTR("client has neither application-identifier nor keychain-access-groups entitlements"));
1295 Query
*q
= query_create_with_limit(attributes
, client
->musr
, 0, error
);
1297 /* Access group sanity checking. */
1298 CFStringRef agrp
= (CFStringRef
)CFDictionaryGetValue(attributes
,
1299 kSecAttrAccessGroup
);
1301 /* Having the special accessGroup "*" allows access to all accessGroups. */
1302 if (CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), CFSTR("*")))
1303 accessGroups
= NULL
;
1306 /* The user specified an explicit access group, validate it. */
1307 if (!accessGroupsAllows(accessGroups
, agrp
))
1308 ok
= SecError(errSecMissingEntitlement
, error
,
1309 CFSTR("explicit accessGroup %@ not in client access %@"), agrp
, accessGroups
);
1311 agrp
= (CFStringRef
)CFArrayGetValueAtIndex(client
->accessGroups
, 0);
1313 /* We are using an implicit access group, add it as if the user
1314 specified it as an attribute. */
1315 query_add_attribute(kSecAttrAccessGroup
, agrp
, q
);
1317 #if TARGET_OS_IPHONE
1318 if (q
->q_system_keychain
&& client
->inMultiUser
) {
1319 CFReleaseNull(q
->q_musrView
);
1320 q
->q_musrView
= SecMUSRCopySystemKeychainUUID();
1322 q
->q_system_keychain
= false;
1324 query_add_attribute_with_desc(&v8musr
, q
->q_musrView
, q
);
1328 query_ensure_access_control(q
, agrp
);
1330 if (q
->q_system_keychain
&& !client
->allowSystemKeychain
) {
1331 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for system keychain"));
1332 } else if (q
->q_sync_bubble
&& !client
->allowSyncBubbleKeychain
) {
1333 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for syncbubble keychain"));
1334 #if TARGET_OS_IPHONE
1335 } else if (q
->q_system_keychain
&& SecItemSynchronizable(attributes
) && !client
->inMultiUser
) {
1336 ok
= SecError(errSecInvalidKey
, error
, CFSTR("Can't store system keychain and synchronizable"));
1338 } else if (q
->q_row_id
) {
1339 ok
= SecError(errSecValuePersistentRefUnsupported
, error
, CFSTR("q_row_id")); // TODO: better error string
1340 } else if (!q
->q_error
) {
1341 ok
= kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
){
1342 return kc_transaction(dbt
, error
, ^{
1343 query_pre_add(q
, true);
1344 return s3dl_query_add(dbt
, q
, result
, error
);
1349 ok
= query_notify_and_destroy(q
, ok
, error
);
1356 /* AUDIT[securityd](done):
1357 query (ok) and attributesToUpdate (ok) are a caller provided dictionaries,
1358 only their cf types have been checked.
1361 _SecItemUpdate(CFDictionaryRef query
, CFDictionaryRef attributesToUpdate
,
1362 SecurityClient
*client
, CFErrorRef
*error
)
1364 CFArrayRef accessGroups
= client
->accessGroups
;
1367 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
)) ||
1368 (ag_count
== 1 && CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), kSecAttrAccessGroupToken
))) {
1369 if (SecTaskDiagnoseEntitlements
)
1370 SecTaskDiagnoseEntitlements(accessGroups
);
1371 return SecError(errSecMissingEntitlement
, error
,
1372 CFSTR("client has neither application-identifier nor keychain-access-groups entitlements"));
1375 if (CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), CFSTR("*"))) {
1376 /* Having the special accessGroup "*" allows access to all accessGroups. */
1377 accessGroups
= NULL
;
1381 Query
*q
= query_create_with_limit(query
, client
->musr
, kSecMatchUnlimited
, error
);
1386 #if TARGET_OS_IPHONE
1387 if (q
->q_system_keychain
&& client
->inMultiUser
) {
1388 CFReleaseNull(q
->q_musrView
);
1389 q
->q_musrView
= SecMUSRCopySystemKeychainUUID();
1391 q
->q_system_keychain
= false;
1395 /* Sanity check the query. */
1396 query_set_caller_access_groups(q
, accessGroups
);
1397 if (q
->q_system_keychain
&& !client
->allowSystemKeychain
) {
1398 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for system keychain"));
1399 } else if (q
->q_sync_bubble
&& !client
->allowSyncBubbleKeychain
) {
1400 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for syncbubble keychain"));
1401 #if TARGET_OS_IPHONE
1402 } else if (q
->q_system_keychain
&& SecItemSynchronizable(attributesToUpdate
) && !client
->inMultiUser
) {
1403 ok
= SecError(errSecInvalidKey
, error
, CFSTR("Can't update an system keychain item with synchronizable"));
1405 } else if (q
->q_use_item_list
) {
1406 ok
= SecError(errSecUseItemListUnsupported
, error
, CFSTR("use item list not supported"));
1407 } else if (q
->q_return_type
& kSecReturnDataMask
) {
1408 /* Update doesn't return anything so don't ask for it. */
1409 ok
= SecError(errSecReturnDataUnsupported
, error
, CFSTR("return data not supported by update"));
1410 } else if (q
->q_return_type
& kSecReturnAttributesMask
) {
1411 ok
= SecError(errSecReturnAttributesUnsupported
, error
, CFSTR("return attributes not supported by update"));
1412 } else if (q
->q_return_type
& kSecReturnRefMask
) {
1413 ok
= SecError(errSecReturnRefUnsupported
, error
, CFSTR("return ref not supported by update"));
1414 } else if (q
->q_return_type
& kSecReturnPersistentRefMask
) {
1415 ok
= SecError(errSecReturnPersistentRefUnsupported
, error
, CFSTR("return persistent ref not supported by update"));
1417 /* Access group sanity checking. */
1418 CFStringRef agrp
= (CFStringRef
)CFDictionaryGetValue(attributesToUpdate
,
1419 kSecAttrAccessGroup
);
1421 /* The user is attempting to modify the access group column,
1422 validate it to make sure the new value is allowable. */
1423 if (!accessGroupsAllows(accessGroups
, agrp
)) {
1424 ok
= SecError(errSecNoAccessForItem
, error
, CFSTR("accessGroup %@ not in %@"), agrp
, accessGroups
);
1430 ok
= kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
1431 return kc_transaction(dbt
, error
, ^{
1432 return s3dl_query_update(dbt
, q
, attributesToUpdate
, accessGroups
, error
);
1437 ok
= query_notify_and_destroy(q
, ok
, error
);
1443 /* AUDIT[securityd](done):
1444 query (ok) is a caller provided dictionary, only its cf type has been checked.
1447 _SecItemDelete(CFDictionaryRef query
, SecurityClient
*client
, CFErrorRef
*error
)
1449 CFArrayRef accessGroups
= client
->accessGroups
;
1452 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
)) ||
1453 (ag_count
== 1 && CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), kSecAttrAccessGroupToken
))) {
1454 if (SecTaskDiagnoseEntitlements
)
1455 SecTaskDiagnoseEntitlements(accessGroups
);
1456 return SecError(errSecMissingEntitlement
, error
,
1457 CFSTR("client has neither application-identifier nor keychain-access-groups entitlements"));
1460 if (CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), CFSTR("*"))) {
1461 /* Having the special accessGroup "*" allows access to all accessGroups. */
1462 accessGroups
= NULL
;
1465 Query
*q
= query_create_with_limit(query
, client
->musr
, kSecMatchUnlimited
, error
);
1468 #if TARGET_OS_IPHONE
1469 if (q
->q_system_keychain
&& client
->inMultiUser
) {
1470 CFReleaseNull(q
->q_musrView
);
1471 q
->q_musrView
= SecMUSRCopySystemKeychainUUID();
1473 q
->q_system_keychain
= false;
1477 query_set_caller_access_groups(q
, accessGroups
);
1478 /* Sanity check the query. */
1479 if (q
->q_system_keychain
&& !client
->allowSystemKeychain
) {
1480 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for system keychain"));
1481 } else if (q
->q_sync_bubble
&& !client
->allowSyncBubbleKeychain
) {
1482 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for syncbubble keychain"));
1483 } else if (q
->q_limit
!= kSecMatchUnlimited
) {
1484 ok
= SecError(errSecMatchLimitUnsupported
, error
, CFSTR("match limit not supported by delete"));
1485 } else if (query_match_count(q
) != 0) {
1486 ok
= SecError(errSecItemMatchUnsupported
, error
, CFSTR("match not supported by delete"));
1487 } else if (q
->q_ref
) {
1488 ok
= SecError(errSecValueRefUnsupported
, error
, CFSTR("value ref not supported by delete"));
1489 } else if (q
->q_row_id
&& query_attr_count(q
)) {
1490 ok
= SecError(errSecItemIllegalQuery
, error
, CFSTR("rowid and other attributes are mutually exclusive"));
1492 ok
= kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
1493 return kc_transaction(dbt
, error
, ^{
1494 return s3dl_query_delete(dbt
, q
, accessGroups
, error
);
1498 ok
= query_notify_and_destroy(q
, ok
, error
);
1505 static bool SecItemDeleteTokenItems(SecDbConnectionRef dbt
, CFTypeRef classToDelete
, CFTypeRef tokenID
, CFArrayRef accessGroups
, SecurityClient
*client
, CFErrorRef
*error
) {
1506 CFTypeRef keys
[] = { kSecClass
, kSecAttrTokenID
};
1507 CFTypeRef values
[] = { classToDelete
, tokenID
};
1509 CFDictionaryRef query
= CFDictionaryCreate(kCFAllocatorDefault
, keys
, values
, 2, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1510 Query
*q
= query_create_with_limit(query
, client
->musr
, kSecMatchUnlimited
, error
);
1514 query_set_caller_access_groups(q
, accessGroups
);
1515 ok
= s3dl_query_delete(dbt
, q
, accessGroups
, error
);
1516 ok
= query_notify_and_destroy(q
, ok
, error
);
1524 static bool SecItemAddTokenItem(SecDbConnectionRef dbt
, CFDictionaryRef attributes
, CFArrayRef accessGroups
, SecurityClient
*client
, CFErrorRef
*error
) {
1526 Query
*q
= query_create_with_limit(attributes
, client
->musr
, 0, error
);
1528 CFStringRef agrp
= kSecAttrAccessGroupToken
;
1529 query_add_attribute(kSecAttrAccessGroup
, agrp
, q
);
1532 query_ensure_access_control(q
, agrp
);
1533 if (q
->q_system_keychain
&& !client
->allowSystemKeychain
) {
1534 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for system keychain"));
1535 } else if (q
->q_sync_bubble
&& !client
->allowSyncBubbleKeychain
) {
1536 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for syncbubble keychain"));
1537 } else if (q
->q_row_id
) {
1538 ok
= SecError(errSecValuePersistentRefUnsupported
, error
, CFSTR("q_row_id")); // TODO: better error string
1539 } else if (!q
->q_error
) {
1540 query_pre_add(q
, true);
1541 ok
= s3dl_query_add(dbt
, q
, NULL
, error
);
1544 ok
= query_notify_and_destroy(q
, ok
, error
);
1551 bool _SecItemUpdateTokenItems(CFStringRef tokenID
, CFArrayRef items
, SecurityClient
*client
, CFErrorRef
*error
) {
1553 CFArrayRef accessGroups
= client
->accessGroups
;
1555 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
))) {
1556 if (SecTaskDiagnoseEntitlements
)
1557 SecTaskDiagnoseEntitlements(accessGroups
);
1558 return SecError(errSecMissingEntitlement
, error
,
1559 CFSTR("client has neither application-identifier nor keychain-access-groups entitlements"));
1562 ok
= kc_with_dbt(true, error
, ^bool (SecDbConnectionRef dbt
) {
1563 return kc_transaction(dbt
, error
, ^bool {
1565 const CFTypeRef classToDelete
[] = { kSecClassGenericPassword
, kSecClassInternetPassword
, kSecClassCertificate
, kSecClassKey
};
1566 for (size_t i
= 0; i
< sizeof(classToDelete
) / sizeof(classToDelete
[0]); ++i
) {
1567 SecItemDeleteTokenItems(dbt
, classToDelete
[i
], tokenID
, accessGroups
, client
, NULL
);
1570 for (CFIndex i
= 0; i
< CFArrayGetCount(items
); ++i
) {
1571 if (!SecItemAddTokenItem(dbt
, CFArrayGetValueAtIndex(items
, i
), accessGroups
, client
, error
))
1577 const CFTypeRef classToDelete
[] = { kSecClassGenericPassword
, kSecClassInternetPassword
, kSecClassCertificate
, kSecClassKey
};
1578 bool deleted
= true;
1579 for (size_t i
= 0; i
< sizeof(classToDelete
) / sizeof(classToDelete
[0]); ++i
) {
1580 if (!SecItemDeleteTokenItems(dbt
, classToDelete
[i
], tokenID
, accessGroups
, client
, error
) && error
&& CFErrorGetCode(*error
) != errSecItemNotFound
) {
1584 else if (error
&& *error
) {
1585 CFReleaseNull(*error
);
1596 /* AUDIT[securityd](done):
1597 No caller provided inputs.
1600 SecItemServerDeleteAll(CFErrorRef
*error
) {
1601 return kc_with_dbt(true, error
, ^bool (SecDbConnectionRef dbt
) {
1602 return (kc_transaction(dbt
, error
, ^bool {
1603 return (SecDbExec(dbt
, CFSTR("DELETE from genp;"), error
) &&
1604 SecDbExec(dbt
, CFSTR("DELETE from inet;"), error
) &&
1605 SecDbExec(dbt
, CFSTR("DELETE from cert;"), error
) &&
1606 SecDbExec(dbt
, CFSTR("DELETE from keys;"), error
));
1607 }) && SecDbExec(dbt
, CFSTR("VACUUM;"), error
));
1612 _SecItemDeleteAll(CFErrorRef
*error
) {
1613 return SecItemServerDeleteAll(error
);
1617 _SecItemServerDeleteAllWithAccessGroups(CFArrayRef accessGroups
, SecurityClient
*client
, CFErrorRef
*error
)
1619 __block
bool ok
= true;
1620 static dispatch_once_t onceToken
;
1621 static CFSetRef illegalAccessGroups
= NULL
;
1623 dispatch_once(&onceToken
, ^{
1624 const CFStringRef values
[] = {
1627 CFSTR("com.apple.security.sos"),
1628 CFSTR("lockdown-identities"),
1630 illegalAccessGroups
= CFSetCreate(NULL
, (const void **)values
, sizeof(values
)/sizeof(values
[0]), &kCFTypeSetCallBacks
);
1633 static const CFTypeRef qclasses
[] = {
1640 require_action_quiet(isArray(accessGroups
), fail
,
1642 SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType
, sSecXPCErrorDomain
, NULL
, error
, NULL
, CFSTR("accessGroups not CFArray, got %@"), accessGroups
));
1644 // TODO: whitelist instead? look for dev IDs like 7123498YQX.com.somedev.app
1646 require_action(CFArrayGetCount(accessGroups
) != 0, fail
,
1648 SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType
, sSecXPCErrorDomain
, NULL
, error
, NULL
, CFSTR("accessGroups e empty")));
1651 // Pre-check accessGroups for prohibited values
1652 CFArrayForEach(accessGroups
, ^(const void *value
) {
1653 CFStringRef agrp
= (CFStringRef
)value
;
1655 if (!isString(agrp
)) {
1656 SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType
, sSecXPCErrorDomain
, NULL
, error
, NULL
,
1657 CFSTR("access not a string: %@"), agrp
);
1659 } else if (CFSetContainsValue(illegalAccessGroups
, agrp
)) {
1660 SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType
, sSecXPCErrorDomain
, NULL
, error
, NULL
,
1661 CFSTR("illegal access group: %@"), accessGroups
);
1667 ok
= kc_with_dbt(true, error
, ^bool(SecDbConnectionRef dbt
) {
1668 return kc_transaction(dbt
, error
, ^bool {
1669 CFErrorRef localError
= NULL
;
1673 for (n
= 0; n
< sizeof(qclasses
)/sizeof(qclasses
[0]) && ok1
; n
++) {
1676 q
= query_create(qclasses
[n
], client
->musr
, NULL
, error
);
1679 (void)s3dl_query_delete(dbt
, q
, accessGroups
, &localError
);
1681 query_destroy(q
, error
);
1682 CFReleaseNull(localError
);
1685 }) && SecDbExec(dbt
, CFSTR("VACUUM"), error
);
1694 // MARK: Shared web credentials
1699 #define SEC_CONST_DECL(k,v) const CFStringRef k = CFSTR(v);
1701 SEC_CONST_DECL (kSecSafariAccessGroup
, "com.apple.cfnetwork");
1702 SEC_CONST_DECL (kSecSafariDefaultComment
, "default");
1703 SEC_CONST_DECL (kSecSafariPasswordsNotSaved
, "Passwords not saved");
1704 SEC_CONST_DECL (kSecSharedCredentialUrlScheme
, "https://");
1705 SEC_CONST_DECL (kSecSharedWebCredentialsService
, "webcredentials");
1707 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1708 static dispatch_once_t sSecSWCInitializeOnce
= 0;
1709 static void * sSecSWCLibrary
= NULL
;
1710 static SWCCheckService_f sSWCCheckService_f
= NULL
;
1711 static SWCSetServiceFlags_f sSWCSetServiceFlags_f
= NULL
;
1713 static OSStatus
_SecSWCEnsuredInitialized(void);
1715 static OSStatus
_SecSWCEnsuredInitialized(void)
1717 __block OSStatus status
= errSecNotAvailable
;
1719 dispatch_once(&sSecSWCInitializeOnce
, ^{
1720 sSecSWCLibrary
= dlopen("/System/Library/PrivateFrameworks/SharedWebCredentials.framework/SharedWebCredentials", RTLD_LAZY
| RTLD_LOCAL
);
1721 assert(sSecSWCLibrary
);
1722 if (sSecSWCLibrary
) {
1723 sSWCCheckService_f
= (SWCCheckService_f
)(uintptr_t) dlsym(sSecSWCLibrary
, "SWCCheckService");
1724 sSWCSetServiceFlags_f
= (SWCSetServiceFlags_f
)(uintptr_t) dlsym(sSecSWCLibrary
, "SWCSetServiceFlags");
1728 if (sSWCCheckService_f
&& sSWCSetServiceFlags_f
) {
1735 #if !TARGET_IPHONE_SIMULATOR
1737 _SecAppDomainApprovalStatus(CFStringRef appID
, CFStringRef fqdn
, CFErrorRef
*error
)
1739 __block SWCFlags flags
= kSWCFlags_None
;
1741 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1742 OSStatus status
= _SecSWCEnsuredInitialized();
1744 SecError(status
, error
, CFSTR("SWC initialize failed"));
1747 CFRetainSafe(appID
);
1749 dispatch_semaphore_t semaphore
= dispatch_semaphore_create(0);
1750 dispatch_retain(semaphore
);
1751 if (0 == sSWCCheckService_f(kSecSharedWebCredentialsService
, appID
, fqdn
,
1752 ^void (OSStatus inStatus
, SWCFlags inFlags
, CFDictionaryRef inDetails
) {
1753 if (!inStatus
) { flags
= inFlags
; }
1754 CFReleaseSafe(appID
);
1755 CFReleaseSafe(fqdn
);
1756 dispatch_semaphore_signal(semaphore
);
1757 dispatch_release(semaphore
);
1758 //secerror("SWCCheckService: inStatus=%d, flags=%0X", inStatus, flags);
1761 // wait for the block to complete, as we need its answer
1762 dispatch_semaphore_wait(semaphore
, DISPATCH_TIME_FOREVER
);
1764 else // didn't queue the block
1766 CFReleaseSafe(appID
);
1767 CFReleaseSafe(fqdn
);
1768 dispatch_release(semaphore
);
1770 dispatch_release(semaphore
);
1772 flags
|= (kSWCFlag_SiteApproved
);
1775 if (!error
) { return flags
; }
1778 // check website approval status
1779 if (!(flags
& kSWCFlag_SiteApproved
)) {
1780 if (flags
& kSWCFlag_Pending
) {
1781 SecError(errSecAuthFailed
, error
, CFSTR("Approval is pending for \"%@\", try later"), fqdn
);
1783 SecError(errSecAuthFailed
, error
, CFSTR("\"%@\" failed to approve \"%@\""), fqdn
, appID
);
1788 // check user approval status
1789 if (flags
& kSWCFlag_UserDenied
) {
1790 SecError(errSecAuthFailed
, error
, CFSTR("User denied access to \"%@\" by \"%@\""), fqdn
, appID
);
1796 #if !TARGET_IPHONE_SIMULATOR
1798 _SecEntitlementContainsDomainForService(CFArrayRef domains
, CFStringRef domain
, CFStringRef service
)
1800 bool result
= false;
1801 CFIndex idx
, count
= (domains
) ? CFArrayGetCount(domains
) : (CFIndex
) 0;
1802 if (!count
|| !domain
|| !service
) {
1805 for (idx
=0; idx
< count
; idx
++) {
1806 CFStringRef str
= (CFStringRef
) CFArrayGetValueAtIndex(domains
, idx
);
1807 if (str
&& CFStringHasPrefix(str
, kSecSharedWebCredentialsService
)) {
1808 CFIndex prefix_len
= CFStringGetLength(kSecSharedWebCredentialsService
)+1;
1809 CFIndex substr_len
= CFStringGetLength(str
) - prefix_len
;
1810 CFRange range
= { prefix_len
, substr_len
};
1811 CFStringRef substr
= CFStringCreateWithSubstring(kCFAllocatorDefault
, str
, range
);
1812 if (substr
&& CFEqual(substr
, domain
)) {
1815 CFReleaseSafe(substr
);
1826 _SecAddNegativeWebCredential(SecurityClient
*client
, CFStringRef fqdn
, CFStringRef appID
, bool forSafari
)
1828 bool result
= false;
1829 if (!fqdn
) { return result
; }
1831 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1832 OSStatus status
= _SecSWCEnsuredInitialized();
1833 if (status
) { return false; }
1835 // update our database
1836 CFRetainSafe(appID
);
1838 if (0 == sSWCSetServiceFlags_f(kSecSharedWebCredentialsService
,
1839 appID
, fqdn
, kSWCFlag_ExternalMask
, kSWCFlag_UserDenied
,
1840 ^void(OSStatus inStatus
, SWCFlags inNewFlags
){
1841 CFReleaseSafe(appID
);
1842 CFReleaseSafe(fqdn
);
1847 else // didn't queue the block
1849 CFReleaseSafe(appID
);
1850 CFReleaseSafe(fqdn
);
1853 if (!forSafari
) { return result
; }
1855 // below this point: create a negative Safari web credential item
1857 CFMutableDictionaryRef attrs
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1858 if (!attrs
) { return result
; }
1860 CFErrorRef error
= NULL
;
1861 CFStringRef accessGroup
= CFSTR("*");
1862 SecurityClient swcclient
= {
1864 .accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&accessGroup
, 1, &kCFTypeArrayCallBacks
),
1865 .allowSystemKeychain
= false,
1866 .allowSyncBubbleKeychain
= false,
1867 .isNetworkExtension
= false,
1868 .musr
= client
->musr
,
1871 CFDictionaryAddValue(attrs
, kSecClass
, kSecClassInternetPassword
);
1872 CFDictionaryAddValue(attrs
, kSecAttrAccessGroup
, kSecSafariAccessGroup
);
1873 CFDictionaryAddValue(attrs
, kSecAttrAuthenticationType
, kSecAttrAuthenticationTypeHTMLForm
);
1874 CFDictionaryAddValue(attrs
, kSecAttrProtocol
, kSecAttrProtocolHTTPS
);
1875 CFDictionaryAddValue(attrs
, kSecAttrServer
, fqdn
);
1876 CFDictionaryAddValue(attrs
, kSecAttrSynchronizable
, kCFBooleanTrue
);
1878 (void)_SecItemDelete(attrs
, &swcclient
, &error
);
1879 CFReleaseNull(error
);
1881 CFDictionaryAddValue(attrs
, kSecAttrAccount
, kSecSafariPasswordsNotSaved
);
1882 CFDictionaryAddValue(attrs
, kSecAttrComment
, kSecSafariDefaultComment
);
1884 CFStringRef label
= CFStringCreateWithFormat(kCFAllocatorDefault
,
1885 NULL
, CFSTR("%@ (%@)"), fqdn
, kSecSafariPasswordsNotSaved
);
1887 CFDictionaryAddValue(attrs
, kSecAttrLabel
, label
);
1888 CFReleaseSafe(label
);
1892 CFDataRef data
= CFDataCreate(kCFAllocatorDefault
, &space
, 1);
1894 CFDictionarySetValue(attrs
, kSecValueData
, data
);
1895 CFReleaseSafe(data
);
1898 CFTypeRef addResult
= NULL
;
1899 result
= _SecItemAdd(attrs
, &swcclient
, &addResult
, &error
);
1901 CFReleaseSafe(addResult
);
1902 CFReleaseSafe(error
);
1903 CFReleaseSafe(attrs
);
1904 CFReleaseSafe(swcclient
.accessGroups
);
1909 /* Specialized version of SecItemAdd for shared web credentials */
1911 _SecAddSharedWebCredential(CFDictionaryRef attributes
,
1912 SecurityClient
*client
,
1913 const audit_token_t
*clientAuditToken
,
1920 SecurityClient swcclient
= {};
1922 CFStringRef fqdn
= CFRetainSafe(CFDictionaryGetValue(attributes
, kSecAttrServer
));
1923 CFStringRef account
= CFDictionaryGetValue(attributes
, kSecAttrAccount
);
1924 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH && !TARGET_OS_TV
1925 CFStringRef password
= CFDictionaryGetValue(attributes
, kSecSharedPassword
);
1927 CFStringRef password
= CFDictionaryGetValue(attributes
, CFSTR("spwd"));
1929 CFStringRef accessGroup
= CFSTR("*");
1930 CFMutableDictionaryRef query
= NULL
, attrs
= NULL
;
1934 // check autofill enabled status
1935 if (!swca_autofill_enabled(clientAuditToken
)) {
1936 SecError(errSecBadReq
, error
, CFSTR("Autofill is not enabled in Safari settings"));
1940 // parse fqdn with CFURL here, since it could be specified as domain:port
1942 CFStringRef urlStr
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%@%@"), kSecSharedCredentialUrlScheme
, fqdn
);
1944 CFURLRef url
= CFURLCreateWithString(kCFAllocatorDefault
, urlStr
, nil
);
1946 CFStringRef hostname
= CFURLCopyHostName(url
);
1948 CFReleaseSafe(fqdn
);
1950 port
= CFURLGetPortNumber(url
);
1954 CFReleaseSafe(urlStr
);
1959 SecError(errSecParam
, error
, CFSTR("No account provided"));
1963 SecError(errSecParam
, error
, CFSTR("No domain provided"));
1967 #if TARGET_IPHONE_SIMULATOR
1968 secerror("app/site association entitlements not checked in Simulator");
1970 OSStatus status
= errSecMissingEntitlement
;
1971 // validate that fqdn is part of caller's shared credential domains entitlement
1973 SecError(status
, error
, CFSTR("Missing application-identifier entitlement"));
1976 if (_SecEntitlementContainsDomainForService(domains
, fqdn
, kSecSharedWebCredentialsService
)) {
1977 status
= errSecSuccess
;
1979 if (errSecSuccess
!= status
) {
1980 CFStringRef msg
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
,
1981 CFSTR("%@ not found in %@ entitlement"), fqdn
, kSecEntitlementAssociatedDomains
);
1983 msg
= CFRetain(CFSTR("Requested domain not found in entitlement"));
1985 SecError(status
, error
, CFSTR("%@"), msg
);
1991 #if TARGET_IPHONE_SIMULATOR
1992 secerror("Ignoring app/site approval state in the Simulator.");
1994 // get approval status for this app/domain pair
1995 SWCFlags flags
= _SecAppDomainApprovalStatus(appID
, fqdn
, error
);
1996 if (!(flags
& kSWCFlag_SiteApproved
)) {
2001 // give ourselves access to see matching items for kSecSafariAccessGroup
2002 swcclient
.task
= NULL
;
2003 swcclient
.accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&accessGroup
, 1, &kCFTypeArrayCallBacks
);
2004 swcclient
.allowSystemKeychain
= false;
2005 swcclient
.musr
= client
->musr
;
2006 swcclient
.allowSystemKeychain
= false;
2007 swcclient
.allowSyncBubbleKeychain
= false;
2008 swcclient
.isNetworkExtension
= false;
2011 // create lookup query
2012 query
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2014 SecError(errSecAllocate
, error
, CFSTR("Unable to create query dictionary"));
2017 CFDictionaryAddValue(query
, kSecClass
, kSecClassInternetPassword
);
2018 CFDictionaryAddValue(query
, kSecAttrAccessGroup
, kSecSafariAccessGroup
);
2019 CFDictionaryAddValue(query
, kSecAttrAuthenticationType
, kSecAttrAuthenticationTypeHTMLForm
);
2020 CFDictionaryAddValue(query
, kSecAttrServer
, fqdn
);
2021 CFDictionaryAddValue(query
, kSecAttrSynchronizable
, kCFBooleanTrue
);
2023 // check for presence of Safari's negative entry ('passwords not saved')
2024 CFDictionarySetValue(query
, kSecAttrAccount
, kSecSafariPasswordsNotSaved
);
2025 ok
= _SecItemCopyMatching(query
, &swcclient
, result
, error
);
2026 if(result
) CFReleaseNull(*result
);
2027 if (error
) CFReleaseNull(*error
);
2029 SecError(errSecDuplicateItem
, error
, CFSTR("Item already exists for this server"));
2033 // now use the provided account (and optional port number, if one was present)
2034 CFDictionarySetValue(query
, kSecAttrAccount
, account
);
2035 if (port
< -1 || port
> 0) {
2036 SInt16 portValueShort
= (port
& 0xFFFF);
2037 CFNumberRef portNumber
= CFNumberCreate(NULL
, kCFNumberSInt16Type
, &portValueShort
);
2038 CFDictionaryAddValue(query
, kSecAttrPort
, portNumber
);
2039 CFReleaseSafe(portNumber
);
2042 // look up existing password
2043 if (_SecItemCopyMatching(query
, &swcclient
, result
, error
)) {
2044 // found it, so this becomes either an "update password" or "delete password" operation
2045 if(result
) CFReleaseNull(*result
);
2046 if(error
) CFReleaseNull(*error
);
2047 bool update
= (password
!= NULL
);
2049 attrs
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2050 CFDataRef credential
= CFStringCreateExternalRepresentation(kCFAllocatorDefault
, password
, kCFStringEncodingUTF8
, 0);
2051 CFDictionaryAddValue(attrs
, kSecValueData
, credential
);
2052 CFReleaseSafe(credential
);
2053 CFDictionaryAddValue(attrs
, kSecAttrComment
, kSecSafariDefaultComment
);
2055 // confirm the update
2056 // (per rdar://16676310 we always prompt, even if there was prior user approval)
2057 ok
= /*approved ||*/ swca_confirm_operation(swca_update_request_id
, clientAuditToken
, query
, error
,
2058 ^void (CFStringRef fqdn
) { _SecAddNegativeWebCredential(client
, fqdn
, appID
, false); });
2060 ok
= _SecItemUpdate(query
, attrs
, &swcclient
, error
);
2064 // confirm the delete
2065 // (per rdar://16676288 we always prompt, even if there was prior user approval)
2066 ok
= /*approved ||*/ swca_confirm_operation(swca_delete_request_id
, clientAuditToken
, query
, error
,
2067 ^void (CFStringRef fqdn
) { _SecAddNegativeWebCredential(client
, fqdn
, appID
, false); });
2069 ok
= _SecItemDelete(query
, &swcclient
, error
);
2073 if (error
) CFReleaseNull(*error
);
2077 if (result
) CFReleaseNull(*result
);
2078 if (error
) CFReleaseNull(*error
);
2080 // password does not exist, so prepare to add it
2082 // a NULL password value removes the existing credential. Since we didn't find it, this is a no-op.
2087 CFStringRef label
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%@ (%@)"), fqdn
, account
);
2089 CFDictionaryAddValue(query
, kSecAttrLabel
, label
);
2090 CFReleaseSafe(label
);
2092 // NOTE: we always expect to use HTTPS for web forms.
2093 CFDictionaryAddValue(query
, kSecAttrProtocol
, kSecAttrProtocolHTTPS
);
2095 CFDataRef credential
= CFStringCreateExternalRepresentation(kCFAllocatorDefault
, password
, kCFStringEncodingUTF8
, 0);
2096 CFDictionarySetValue(query
, kSecValueData
, credential
);
2097 CFReleaseSafe(credential
);
2098 CFDictionarySetValue(query
, kSecAttrComment
, kSecSafariDefaultComment
);
2100 CFReleaseSafe(swcclient
.accessGroups
);
2101 swcclient
.accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&kSecSafariAccessGroup
, 1, &kCFTypeArrayCallBacks
);
2103 // mark the item as created by this function
2104 const int32_t creator_value
= 'swca';
2105 CFNumberRef creator
= CFNumberCreate(kCFAllocatorDefault
, kCFNumberSInt32Type
, &creator_value
);
2107 CFDictionarySetValue(query
, kSecAttrCreator
, creator
);
2108 CFReleaseSafe(creator
);
2113 // (per rdar://16680019, we won't prompt here in the normal case)
2114 ok
= /*approved ||*/ swca_confirm_operation(swca_add_request_id
, clientAuditToken
, query
, error
,
2115 ^void (CFStringRef fqdn
) { _SecAddNegativeWebCredential(client
, fqdn
, appID
, false); });
2119 ok
= _SecItemAdd(query
, &swcclient
, result
, error
);
2123 CFReleaseSafe(attrs
);
2124 CFReleaseSafe(query
);
2125 CFReleaseSafe(swcclient
.accessGroups
);
2126 CFReleaseSafe(fqdn
);
2130 /* Specialized version of SecItemCopyMatching for shared web credentials */
2132 _SecCopySharedWebCredential(CFDictionaryRef query
,
2133 SecurityClient
*client
,
2134 const audit_token_t
*clientAuditToken
,
2140 CFMutableArrayRef credentials
= NULL
;
2141 CFMutableArrayRef foundItems
= NULL
;
2142 CFMutableArrayRef fqdns
= NULL
;
2143 CFStringRef fqdn
= NULL
;
2144 CFStringRef account
= NULL
;
2149 require_quiet(result
, cleanup
);
2150 credentials
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
2151 foundItems
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
2152 fqdns
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
2154 // give ourselves access to see matching items for kSecSafariAccessGroup
2155 CFStringRef accessGroup
= CFSTR("*");
2156 SecurityClient swcclient
= {
2158 .accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&accessGroup
, 1, &kCFTypeArrayCallBacks
),
2159 .allowSystemKeychain
= false,
2160 .allowSyncBubbleKeychain
= false,
2161 .isNetworkExtension
= false,
2162 .musr
= client
->musr
,
2165 // On input, the query dictionary contains optional fqdn and account entries.
2166 fqdn
= CFDictionaryGetValue(query
, kSecAttrServer
);
2167 account
= CFDictionaryGetValue(query
, kSecAttrAccount
);
2169 // Check autofill enabled status
2170 if (!swca_autofill_enabled(clientAuditToken
)) {
2171 SecError(errSecBadReq
, error
, CFSTR("Autofill is not enabled in Safari settings"));
2175 // Check fqdn; if NULL, add domains from caller's entitlement.
2177 CFArrayAppendValue(fqdns
, fqdn
);
2180 CFIndex idx
, count
= CFArrayGetCount(domains
);
2181 for (idx
=0; idx
< count
; idx
++) {
2182 CFStringRef str
= (CFStringRef
) CFArrayGetValueAtIndex(domains
, idx
);
2183 // Parse the entry for our service label prefix
2184 if (str
&& CFStringHasPrefix(str
, kSecSharedWebCredentialsService
)) {
2185 CFIndex prefix_len
= CFStringGetLength(kSecSharedWebCredentialsService
)+1;
2186 CFIndex substr_len
= CFStringGetLength(str
) - prefix_len
;
2187 CFRange range
= { prefix_len
, substr_len
};
2188 fqdn
= CFStringCreateWithSubstring(kCFAllocatorDefault
, str
, range
);
2190 CFArrayAppendValue(fqdns
, fqdn
);
2196 count
= CFArrayGetCount(fqdns
);
2198 SecError(errSecParam
, error
, CFSTR("No domain provided"));
2202 // Aggregate search results for each domain
2203 for (idx
= 0; idx
< count
; idx
++) {
2204 CFMutableArrayRef items
= NULL
;
2205 CFMutableDictionaryRef attrs
= NULL
;
2206 fqdn
= (CFStringRef
) CFArrayGetValueAtIndex(fqdns
, idx
);
2210 // Parse the fqdn for a possible port specifier.
2212 CFStringRef urlStr
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%@%@"), kSecSharedCredentialUrlScheme
, fqdn
);
2214 CFURLRef url
= CFURLCreateWithString(kCFAllocatorDefault
, urlStr
, nil
);
2216 CFStringRef hostname
= CFURLCopyHostName(url
);
2218 CFReleaseSafe(fqdn
);
2220 port
= CFURLGetPortNumber(url
);
2224 CFReleaseSafe(urlStr
);
2228 #if TARGET_IPHONE_SIMULATOR
2229 secerror("app/site association entitlements not checked in Simulator");
2231 OSStatus status
= errSecMissingEntitlement
;
2233 SecError(status
, error
, CFSTR("Missing application-identifier entitlement"));
2234 CFReleaseSafe(fqdn
);
2237 // validate that fqdn is part of caller's entitlement
2238 if (_SecEntitlementContainsDomainForService(domains
, fqdn
, kSecSharedWebCredentialsService
)) {
2239 status
= errSecSuccess
;
2241 if (errSecSuccess
!= status
) {
2242 CFStringRef msg
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
,
2243 CFSTR("%@ not found in %@ entitlement"), fqdn
, kSecEntitlementAssociatedDomains
);
2245 msg
= CFRetain(CFSTR("Requested domain not found in entitlement"));
2247 SecError(status
, error
, CFSTR("%@"), msg
);
2249 CFReleaseSafe(fqdn
);
2254 attrs
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2256 SecError(errSecAllocate
, error
, CFSTR("Unable to create query dictionary"));
2257 CFReleaseSafe(fqdn
);
2260 CFDictionaryAddValue(attrs
, kSecClass
, kSecClassInternetPassword
);
2261 CFDictionaryAddValue(attrs
, kSecAttrAccessGroup
, kSecSafariAccessGroup
);
2262 CFDictionaryAddValue(attrs
, kSecAttrAuthenticationType
, kSecAttrAuthenticationTypeHTMLForm
);
2263 CFDictionaryAddValue(attrs
, kSecAttrServer
, fqdn
);
2265 CFDictionaryAddValue(attrs
, kSecAttrAccount
, account
);
2267 if (port
< -1 || port
> 0) {
2268 SInt16 portValueShort
= (port
& 0xFFFF);
2269 CFNumberRef portNumber
= CFNumberCreate(NULL
, kCFNumberSInt16Type
, &portValueShort
);
2270 CFDictionaryAddValue(attrs
, kSecAttrPort
, portNumber
);
2271 CFReleaseSafe(portNumber
);
2273 CFDictionaryAddValue(attrs
, kSecAttrSynchronizable
, kCFBooleanTrue
);
2274 CFDictionaryAddValue(attrs
, kSecMatchLimit
, kSecMatchLimitAll
);
2275 CFDictionaryAddValue(attrs
, kSecReturnAttributes
, kCFBooleanTrue
);
2276 CFDictionaryAddValue(attrs
, kSecReturnData
, kCFBooleanTrue
);
2278 ok
= _SecItemCopyMatching(attrs
, &swcclient
, (CFTypeRef
*)&items
, error
);
2280 // ignore interim error since we have multiple domains to search
2281 CFReleaseNull(*error
);
2283 if (ok
&& items
&& CFGetTypeID(items
) == CFArrayGetTypeID()) {
2284 #if TARGET_IPHONE_SIMULATOR
2285 secerror("Ignoring app/site approval state in the Simulator.");
2286 bool approved
= true;
2288 // get approval status for this app/domain pair
2289 SWCFlags flags
= _SecAppDomainApprovalStatus(appID
, fqdn
, error
);
2291 // ignore interim error since we have multiple domains to check
2292 CFReleaseNull(*error
);
2294 bool approved
= (flags
& kSWCFlag_SiteApproved
);
2297 CFArrayAppendArray(foundItems
, items
, CFRangeMake(0, CFArrayGetCount(items
)));
2300 CFReleaseSafe(items
);
2301 CFReleaseSafe(attrs
);
2302 CFReleaseSafe(fqdn
);
2305 // If matching credentials are found, the credentials provided to the completionHandler
2306 // will be a CFArrayRef containing CFDictionaryRef entries. Each dictionary entry will
2307 // contain the following pairs (see Security/SecItem.h):
2308 // key: kSecAttrServer value: CFStringRef (the website)
2309 // key: kSecAttrAccount value: CFStringRef (the account)
2310 // key: kSecSharedPassword value: CFStringRef (the password)
2312 // key: kSecAttrPort value: CFNumberRef (the port number, if non-standard for https)
2314 count
= CFArrayGetCount(foundItems
);
2315 for (idx
= 0; idx
< count
; idx
++) {
2316 CFDictionaryRef dict
= (CFDictionaryRef
) CFArrayGetValueAtIndex(foundItems
, idx
);
2317 CFMutableDictionaryRef newdict
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2318 if (newdict
&& dict
&& CFGetTypeID(dict
) == CFDictionaryGetTypeID()) {
2319 CFStringRef srvr
= CFDictionaryGetValue(dict
, kSecAttrServer
);
2320 CFStringRef acct
= CFDictionaryGetValue(dict
, kSecAttrAccount
);
2321 CFNumberRef pnum
= CFDictionaryGetValue(dict
, kSecAttrPort
);
2322 CFStringRef icmt
= CFDictionaryGetValue(dict
, kSecAttrComment
);
2323 CFDataRef data
= CFDictionaryGetValue(dict
, kSecValueData
);
2325 CFDictionaryAddValue(newdict
, kSecAttrServer
, srvr
);
2328 CFDictionaryAddValue(newdict
, kSecAttrAccount
, acct
);
2332 if (CFNumberGetValue(pnum
, kCFNumberSInt16Type
, &pval
) &&
2333 (pval
< -1 || pval
> 0)) {
2334 CFDictionaryAddValue(newdict
, kSecAttrPort
, pnum
);
2338 CFStringRef password
= CFStringCreateFromExternalRepresentation(kCFAllocatorDefault
, data
, kCFStringEncodingUTF8
);
2340 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH && !TARGET_OS_TV
2341 CFDictionaryAddValue(newdict
, kSecSharedPassword
, password
);
2343 CFDictionaryAddValue(newdict
, CFSTR("spwd"), password
);
2345 CFReleaseSafe(password
);
2348 if (icmt
&& CFEqual(icmt
, kSecSafariDefaultComment
)) {
2349 CFArrayInsertValueAtIndex(credentials
, 0, newdict
);
2351 CFArrayAppendValue(credentials
, newdict
);
2354 CFReleaseSafe(newdict
);
2361 // create a new array of dictionaries (without the actual password) for picker UI
2362 count
= CFArrayGetCount(credentials
);
2363 CFMutableArrayRef items
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
2364 for (idx
= 0; idx
< count
; idx
++) {
2365 CFDictionaryRef dict
= (CFDictionaryRef
) CFArrayGetValueAtIndex(credentials
, idx
);
2366 CFMutableDictionaryRef newdict
= CFDictionaryCreateMutableCopy(kCFAllocatorDefault
, 0, dict
);
2367 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH && !TARGET_OS_TV
2368 CFDictionaryRemoveValue(newdict
, kSecSharedPassword
);
2370 CFDictionaryRemoveValue(newdict
, CFSTR("spwd"));
2372 CFArrayAppendValue(items
, newdict
);
2373 CFReleaseSafe(newdict
);
2376 // prompt user to select one of the dictionary items
2377 CFDictionaryRef selected
= swca_copy_selected_dictionary(swca_select_request_id
,
2378 clientAuditToken
, items
, error
);
2380 // find the matching item in our credentials array
2381 CFStringRef srvr
= CFDictionaryGetValue(selected
, kSecAttrServer
);
2382 CFStringRef acct
= CFDictionaryGetValue(selected
, kSecAttrAccount
);
2383 CFNumberRef pnum
= CFDictionaryGetValue(selected
, kSecAttrPort
);
2384 for (idx
= 0; idx
< count
; idx
++) {
2385 CFDictionaryRef dict
= (CFDictionaryRef
) CFArrayGetValueAtIndex(credentials
, idx
);
2386 CFStringRef srvr1
= CFDictionaryGetValue(dict
, kSecAttrServer
);
2387 CFStringRef acct1
= CFDictionaryGetValue(dict
, kSecAttrAccount
);
2388 CFNumberRef pnum1
= CFDictionaryGetValue(dict
, kSecAttrPort
);
2390 if (!srvr
|| !srvr1
|| !CFEqual(srvr
, srvr1
)) continue;
2391 if (!acct
|| !acct1
|| !CFEqual(acct
, acct1
)) continue;
2392 if ((pnum
&& pnum1
) && !CFEqual(pnum
, pnum1
)) continue;
2395 CFReleaseSafe(selected
);
2402 CFReleaseSafe(items
);
2403 CFArrayRemoveAllValues(credentials
);
2404 if (selected
&& ok
) {
2405 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
2406 fqdn
= CFDictionaryGetValue(selected
, kSecAttrServer
);
2408 CFArrayAppendValue(credentials
, selected
);
2412 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
2413 // register confirmation with database
2414 OSStatus status
= _SecSWCEnsuredInitialized();
2416 SecError(status
, error
, CFSTR("SWC initialize failed"));
2418 CFReleaseSafe(selected
);
2421 CFRetainSafe(appID
);
2423 if (0 != sSWCSetServiceFlags_f(kSecSharedWebCredentialsService
,
2424 appID
, fqdn
, kSWCFlag_ExternalMask
, kSWCFlag_UserApproved
,
2425 ^void(OSStatus inStatus
, SWCFlags inNewFlags
){
2426 CFReleaseSafe(appID
);
2427 CFReleaseSafe(fqdn
);
2430 // we didn't queue the block
2431 CFReleaseSafe(appID
);
2432 CFReleaseSafe(fqdn
);
2436 CFReleaseSafe(selected
);
2438 else if (NULL
== *error
) {
2439 // found no items, and we haven't already filled in the error
2440 SecError(errSecItemNotFound
, error
, CFSTR("no matching items found"));
2445 CFArrayRemoveAllValues(credentials
);
2446 CFReleaseNull(credentials
);
2448 CFReleaseSafe(foundItems
);
2449 *result
= credentials
;
2450 CFReleaseSafe(swcclient
.accessGroups
);
2451 CFReleaseSafe(fqdns
);
2456 #endif /* TARGET_OS_IOS */
2460 // MARK: Keychain backup
2462 CF_RETURNS_RETAINED CFDataRef
2463 _SecServerKeychainCreateBackup(SecurityClient
*client
, CFDataRef keybag
, CFDataRef passcode
, CFErrorRef
*error
) {
2465 SecDbConnectionRef dbt
= SecDbConnectionAquire(kc_dbhandle(), false, error
);
2470 if (keybag
== NULL
&& passcode
== NULL
) {
2472 backup
= SecServerExportBackupableKeychain(dbt
, client
, KEYBAG_DEVICE
, backup_keybag_handle
, error
);
2473 #else /* !USE_KEYSTORE */
2475 SecError(errSecParam
, error
, CFSTR("Why are you doing this?"));
2477 #endif /* USE_KEYSTORE */
2479 backup
= SecServerKeychainCreateBackup(dbt
, client
, keybag
, passcode
, error
);
2482 SecDbConnectionRelease(dbt
);
2488 _SecServerKeychainRestore(CFDataRef backup
, SecurityClient
*client
, CFDataRef keybag
, CFDataRef passcode
, CFErrorRef
*error
) {
2489 if (backup
== NULL
|| keybag
== NULL
)
2490 return SecError(errSecParam
, error
, CFSTR("backup or keybag missing"));
2492 __block
bool ok
= true;
2493 ok
&= SecDbPerformWrite(kc_dbhandle(), error
, ^(SecDbConnectionRef dbconn
) {
2494 ok
= SecServerKeychainRestore(dbconn
, client
, backup
, keybag
, passcode
, error
);
2498 SecKeychainChanged(true);
2505 _SecServerBackupCopyUUID(CFDataRef data
, CFErrorRef
*error
)
2507 CFStringRef uuid
= NULL
;
2508 CFDictionaryRef backup
;
2510 backup
= CFPropertyListCreateWithData(kCFAllocatorDefault
, data
,
2511 kCFPropertyListImmutable
, NULL
,
2513 if (isDictionary(backup
)) {
2514 uuid
= SecServerBackupGetKeybagUUID(backup
);
2518 CFReleaseNull(backup
);
2526 // MARK: SecItemDataSource
2528 // Make sure to call this before any writes to the keychain, so that we fire
2529 // up the engines to monitor manifest changes.
2530 SOSDataSourceFactoryRef
SecItemDataSourceFactoryGetDefault(void) {
2531 return SecItemDataSourceFactoryGetShared(kc_dbhandle());
2534 /* AUDIT[securityd]:
2535 args_in (ok) is a caller provided, CFDictionaryRef.
2538 CF_RETURNS_RETAINED CFArrayRef
2539 _SecServerKeychainSyncUpdateMessage(CFDictionaryRef updates
, CFErrorRef
*error
) {
2540 // This never fails, trust us!
2541 return SOSCCHandleUpdateMessage(updates
);
2545 // Truthiness in the cloud backup/restore support.
2548 static CFDictionaryRef
2549 _SecServerCopyTruthInTheCloud(CFDataRef keybag
, CFDataRef password
,
2550 CFDictionaryRef backup
, CFErrorRef
*error
)
2552 SOSManifestRef mold
= NULL
, mnow
= NULL
, mdelete
= NULL
, madd
= NULL
;
2553 __block CFMutableDictionaryRef backup_new
= NULL
;
2554 keybag_handle_t bag_handle
;
2555 if (!ks_open_keybag(keybag
, password
, &bag_handle
, error
))
2558 // We need to have a datasource singleton for protection domain
2559 // kSecAttrAccessibleWhenUnlocked and keep a single shared engine
2560 // instance around which we create in the datasource constructor as well.
2561 SOSDataSourceFactoryRef dsf
= SecItemDataSourceFactoryGetDefault();
2562 SOSDataSourceRef ds
= SOSDataSourceFactoryCreateDataSource(dsf
, kSecAttrAccessibleWhenUnlocked
, error
);
2564 backup_new
= backup
? CFDictionaryCreateMutableCopy(kCFAllocatorDefault
, 0, backup
) : CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2565 mold
= SOSCreateManifestWithBackup(backup
, error
);
2566 SOSEngineRef engine
= SOSDataSourceGetSharedEngine(ds
, error
);
2567 mnow
= SOSEngineCopyManifest(engine
, NULL
);
2569 mnow
= SOSDataSourceCopyManifestWithViewNameSet(ds
, SOSViewsGetV0ViewSet(), error
);
2572 CFReleaseNull(backup_new
);
2573 secerror("failed to obtain manifest for keychain: %@", error
? *error
: NULL
);
2575 SOSManifestDiff(mold
, mnow
, &mdelete
, &madd
, error
);
2578 // Delete everything from the new_backup that is no longer in the datasource according to the datasources manifest.
2579 SOSManifestForEach(mdelete
, ^(CFDataRef digest_data
, bool *stop
) {
2580 CFStringRef deleted_item_key
= CFDataCopyHexString(digest_data
);
2581 CFDictionaryRemoveValue(backup_new
, deleted_item_key
);
2582 CFRelease(deleted_item_key
);
2585 CFMutableArrayRef changes
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
2586 SOSDataSourceForEachObject(ds
, NULL
, madd
, error
, ^void(CFDataRef digest
, SOSObjectRef object
, bool *stop
) {
2587 CFErrorRef localError
= NULL
;
2588 CFDataRef digest_data
= NULL
;
2589 CFTypeRef value
= NULL
;
2591 // Key in our manifest can't be found in db, remove it from our manifest
2592 SOSChangesAppendDelete(changes
, digest
);
2593 } else if (!(digest_data
= SOSObjectCopyDigest(ds
, object
, &localError
))
2594 || !(value
= SOSObjectCopyBackup(ds
, object
, bag_handle
, &localError
))) {
2595 if (SecErrorGetOSStatus(localError
) == errSecDecode
) {
2596 // Ignore decode errors, pretend the objects aren't there
2597 CFRelease(localError
);
2598 // Object undecodable, remove it from our manifest
2599 SOSChangesAppendDelete(changes
, digest
);
2601 // Stop iterating and propagate out all other errors.
2603 *error
= localError
;
2604 CFReleaseNull(backup_new
);
2607 // TODO: Should we skip tombstones here?
2608 CFStringRef key
= CFDataCopyHexString(digest_data
);
2609 CFDictionarySetValue(backup_new
, key
, value
);
2612 CFReleaseSafe(digest_data
);
2613 CFReleaseSafe(value
);
2614 }) || CFReleaseNull(backup_new
);
2616 if (CFArrayGetCount(changes
)) {
2617 if (!SOSEngineUpdateChanges(engine
, kSOSDataSourceSOSTransaction
, changes
, error
)) {
2618 CFReleaseNull(backup_new
);
2621 CFReleaseSafe(changes
);
2623 SOSDataSourceRelease(ds
, error
) || CFReleaseNull(backup_new
);
2626 CFReleaseSafe(mold
);
2627 CFReleaseSafe(mnow
);
2628 CFReleaseSafe(madd
);
2629 CFReleaseSafe(mdelete
);
2630 ks_close_keybag(bag_handle
, error
) || CFReleaseNull(backup_new
);
2636 _SecServerRestoreTruthInTheCloud(CFDataRef keybag
, CFDataRef password
, CFDictionaryRef backup_in
, CFErrorRef
*error
) {
2637 __block
bool ok
= true;
2638 keybag_handle_t bag_handle
;
2639 if (!ks_open_keybag(keybag
, password
, &bag_handle
, error
))
2642 SOSManifestRef mbackup
= SOSCreateManifestWithBackup(backup_in
, error
);
2644 SOSDataSourceFactoryRef dsf
= SecItemDataSourceFactoryGetDefault();
2645 SOSDataSourceRef ds
= SOSDataSourceFactoryCreateDataSource(dsf
, kSecAttrAccessibleWhenUnlocked
, error
);
2646 ok
&= ds
&& SOSDataSourceWith(ds
, error
, ^(SOSTransactionRef txn
, bool *commit
) {
2647 SOSManifestRef mnow
= SOSDataSourceCopyManifestWithViewNameSet(ds
, SOSViewsGetV0BackupViewSet(), error
);
2648 SOSManifestRef mdelete
= NULL
, madd
= NULL
;
2649 SOSManifestDiff(mnow
, mbackup
, &mdelete
, &madd
, error
);
2651 // Don't delete everything in datasource not in backup.
2653 // Add items from the backup
2654 SOSManifestForEach(madd
, ^void(CFDataRef e
, bool *stop
) {
2655 CFDictionaryRef item
= NULL
;
2656 CFStringRef sha1
= CFDataCopyHexString(e
);
2658 item
= CFDictionaryGetValue(backup_in
, sha1
);
2662 CFErrorRef localError
= NULL
;
2664 if (!SOSObjectRestoreObject(ds
, txn
, bag_handle
, item
, &localError
)) {
2665 OSStatus status
= SecErrorGetOSStatus(localError
);
2666 if (status
== errSecDuplicateItem
) {
2667 // Log and ignore duplicate item errors during restore
2668 secnotice("titc", "restore %@ not replacing existing item", item
);
2669 } else if (status
== errSecDecode
) {
2670 // Log and ignore corrupted item errors during restore
2671 secnotice("titc", "restore %@ skipping corrupted item %@", item
, localError
);
2673 if (status
== errSecInteractionNotAllowed
)
2675 // Propagate the first other error upwards (causing the restore to fail).
2676 secerror("restore %@ failed %@", item
, localError
);
2678 if (error
&& !*error
) {
2679 *error
= localError
;
2683 CFReleaseSafe(localError
);
2687 ok
&= SOSDataSourceRelease(ds
, error
);
2688 CFReleaseNull(mdelete
);
2689 CFReleaseNull(madd
);
2690 CFReleaseNull(mnow
);
2695 ok
&= ks_close_keybag(bag_handle
, error
);
2701 CF_RETURNS_RETAINED CFDictionaryRef
2702 _SecServerBackupSyncable(CFDictionaryRef backup
, CFDataRef keybag
, CFDataRef password
, CFErrorRef
*error
) {
2703 require_action_quiet(isData(keybag
), errOut
, SecError(errSecParam
, error
, CFSTR("keybag %@ not a data"), keybag
));
2704 require_action_quiet(!backup
|| isDictionary(backup
), errOut
, SecError(errSecParam
, error
, CFSTR("backup %@ not a dictionary"), backup
));
2705 require_action_quiet(!password
|| isData(password
), errOut
, SecError(errSecParam
, error
, CFSTR("password %@ not a data"), password
));
2707 return _SecServerCopyTruthInTheCloud(keybag
, password
, backup
, error
);
2714 _SecServerRestoreSyncable(CFDictionaryRef backup
, CFDataRef keybag
, CFDataRef password
, CFErrorRef
*error
) {
2716 require_action_quiet(isData(keybag
), errOut
, ok
= SecError(errSecParam
, error
, CFSTR("keybag %@ not a data"), keybag
));
2717 require_action_quiet(isDictionary(backup
), errOut
, ok
= SecError(errSecParam
, error
, CFSTR("backup %@ not a dictionary"), backup
));
2720 require_action_quiet(isData(password
), errOut
, ok
= SecError(errSecParam
, error
, CFSTR("password not a data")));
2723 ok
= _SecServerRestoreTruthInTheCloud(keybag
, password
, backup
, error
);
2729 bool _SecServerRollKeysGlue(bool force
, CFErrorRef
*error
) {
2730 return _SecServerRollKeys(force
, NULL
, error
);
2734 bool _SecServerRollKeys(bool force
, SecurityClient
*client
, CFErrorRef
*error
) {
2736 uint32_t keystore_generation_status
= 0;
2737 if (aks_generation(KEYBAG_DEVICE
, generation_noop
, &keystore_generation_status
))
2739 uint32_t current_generation
= keystore_generation_status
& generation_current
;
2741 return kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
2742 bool up_to_date
= s3dl_dbt_keys_current(dbt
, current_generation
, NULL
);
2744 if (force
&& !up_to_date
) {
2745 up_to_date
= s3dl_dbt_update_keys(dbt
, client
, error
);
2747 secerror("Completed roll keys.");
2748 up_to_date
= s3dl_dbt_keys_current(dbt
, current_generation
, NULL
);
2751 secerror("Failed to roll keys.");
2763 * Sync bubble migration code
2766 struct SyncBubbleRule
{
2767 CFStringRef attribute
;
2772 TransmogrifyItemsToSyncBubble(SecurityClient
*client
, uid_t uid
,
2775 const SecDbClass
*qclass
,
2776 struct SyncBubbleRule
*items
, CFIndex nItems
,
2779 CFMutableDictionaryRef updateAttributes
= NULL
;
2780 CFDataRef syncBubbleView
= NULL
;
2781 CFDataRef activeUserView
= NULL
;
2786 syncBubbleView
= SecMUSRCreateSyncBubbleUserUUID(uid
);
2787 require(syncBubbleView
, fail
);
2789 activeUserView
= SecMUSRCreateActiveUserUUID(uid
);
2790 require(activeUserView
, fail
);
2793 if ((onlyDelete
&& !copyToo
) || !onlyDelete
) {
2796 * Clean out items first
2799 secnotice("syncbubble", "cleaning out old items");
2801 q
= query_create(qclass
, NULL
, NULL
, error
);
2804 q
->q_limit
= kSecMatchUnlimited
;
2805 q
->q_keybag
= device_keybag_handle
;
2807 for (n
= 0; n
< nItems
; n
++) {
2808 query_add_attribute(items
[n
].attribute
, items
[n
].value
, q
);
2810 q
->q_musrView
= CFRetain(syncBubbleView
);
2811 require(q
->q_musrView
, fail
);
2813 kc_with_dbt(false, error
, ^(SecDbConnectionRef dbt
) {
2814 return kc_transaction(dbt
, error
, ^{
2815 return s3dl_query_delete(dbt
, q
, NULL
, error
);
2819 query_destroy(q
, NULL
);
2824 if (onlyDelete
|| !copyToo
) {
2825 secnotice("syncbubble", "skip migration of items");
2828 * Copy over items from EMCS to sync bubble
2831 secnotice("syncbubble", "migrating sync bubble items");
2833 q
= query_create(qclass
, NULL
, NULL
, error
);
2836 q
->q_return_type
= kSecReturnDataMask
| kSecReturnAttributesMask
;
2837 q
->q_limit
= kSecMatchUnlimited
;
2838 q
->q_keybag
= device_keybag_handle
; /* XXX change to session key bag when it exists */
2840 for (n
= 0; n
< nItems
; n
++) {
2841 query_add_or_attribute(items
[n
].attribute
, items
[n
].value
, q
);
2843 query_add_or_attribute(CFSTR("musr"), activeUserView
, q
);
2844 q
->q_musrView
= CFRetain(activeUserView
);
2846 updateAttributes
= CFDictionaryCreateMutableForCFTypes(NULL
);
2847 require(updateAttributes
, fail
);
2849 CFDictionarySetValue(updateAttributes
, CFSTR("musr"), syncBubbleView
); /* XXX should use kSecAttrMultiUser */
2852 kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
2853 return kc_transaction(dbt
, error
, ^{
2854 CFErrorRef error2
= NULL
;
2856 SecDbItemSelect(q
, dbt
, &error2
, NULL
, ^bool(const SecDbAttr
*attr
) {
2857 return CFDictionaryGetValue(q
->q_item
, attr
->name
);
2858 }, NULL
, NULL
, ^(SecDbItemRef item
, bool *stop
) {
2859 CFErrorRef error3
= NULL
;
2860 secinfo("syncbubble", "migrating item");
2862 SecDbItemRef new_item
= SecDbItemCopyWithUpdates(item
, updateAttributes
, NULL
);
2863 if (new_item
== NULL
)
2866 SecDbItemClearRowId(new_item
, NULL
);
2868 if (!SecDbItemSetKeybag(new_item
, device_keybag_handle
, NULL
)) {
2869 CFRelease(new_item
);
2873 if (!SecDbItemInsert(new_item
, dbt
, &error3
)) {
2874 secnotice("syncbubble", "migration failed with %@ for item %@", error3
, new_item
);
2876 CFRelease(new_item
);
2877 CFReleaseNull(error3
);
2879 CFReleaseNull(error2
);
2888 CFReleaseNull(syncBubbleView
);
2889 CFReleaseNull(activeUserView
);
2890 CFReleaseNull(updateAttributes
);
2892 query_destroy(q
, NULL
);
2897 static struct SyncBubbleRule PCSItems
[] = {
2899 .attribute
= CFSTR("agrp"),
2900 .value
= CFSTR("com.apple.ProtectedCloudStorage"),
2903 static struct SyncBubbleRule NSURLSesssiond
[] = {
2905 .attribute
= CFSTR("agrp"),
2906 .value
= CFSTR("com.apple.nsurlsessiond"),
2909 static struct SyncBubbleRule AccountsdItems
[] = {
2911 .attribute
= CFSTR("svce"),
2912 .value
= CFSTR("com.apple.account.AppleAccount.token"),
2915 .attribute
= CFSTR("svce"),
2916 .value
= CFSTR("com.apple.account.AppleAccount.password"),
2919 .attribute
= CFSTR("svce"),
2920 .value
= CFSTR("com.apple.account.AppleAccount.rpassword"),
2923 .attribute
= CFSTR("svce"),
2924 .value
= CFSTR("com.apple.account.idms.token"),
2927 .attribute
= CFSTR("svce"),
2928 .value
= CFSTR("com.apple.account.idms.continuation-key"),
2931 .attribute
= CFSTR("svce"),
2932 .value
= CFSTR("com.apple.account.CloudKit.token"),
2936 static struct SyncBubbleRule MobileMailItems
[] = {
2938 .attribute
= CFSTR("svce"),
2939 .value
= CFSTR("com.apple.account.IMAP.password"),
2942 .attribute
= CFSTR("svce"),
2943 .value
= CFSTR("com.apple.account.SMTP.password"),
2946 .attribute
= CFSTR("svce"),
2947 .value
= CFSTR("com.apple.account.Exchange.password"),
2950 .attribute
= CFSTR("svce"),
2951 .value
= CFSTR("com.apple.account.Hotmail.password"),
2954 .attribute
= CFSTR("svce"),
2955 .value
= CFSTR("com.apple.account.Google.password"),
2958 .attribute
= CFSTR("svce"),
2959 .value
= CFSTR("com.apple.account.Google.oauth-token"),
2962 .attribute
= CFSTR("svce"),
2963 .value
= CFSTR("com.apple.account.Google.oath-refresh-token"),
2966 .attribute
= CFSTR("svce"),
2967 .value
= CFSTR("com.apple.account.Yahoo.password"),
2970 .attribute
= CFSTR("svce"),
2971 .value
= CFSTR("com.apple.account.Yahoo.oauth-token"),
2974 .attribute
= CFSTR("svce"),
2975 .value
= CFSTR("com.apple.account.Yahoo.oauth-token-nosync"),
2978 .attribute
= CFSTR("svce"),
2979 .value
= CFSTR("com.apple.account.Yahoo.oath-refresh-token"),
2982 .attribute
= CFSTR("svce"),
2983 .value
= CFSTR("com.apple.account.IMAPNotes.password"),
2986 .attribute
= CFSTR("svce"),
2987 .value
= CFSTR("com.apple.account.IMAPMail.password"),
2990 .attribute
= CFSTR("svce"),
2991 .value
= CFSTR("com.apple.account.126.password"),
2994 .attribute
= CFSTR("svce"),
2995 .value
= CFSTR("com.apple.account.163.password"),
2998 .attribute
= CFSTR("svce"),
2999 .value
= CFSTR("com.apple.account.aol.password"),
3004 ArrayContains(CFArrayRef array
, CFStringRef service
)
3006 return CFArrayContainsValue(array
, CFRangeMake(0, CFArrayGetCount(array
)), service
);
3010 _SecServerTransmogrifyToSyncBubble(CFArrayRef services
, uid_t uid
, SecurityClient
*client
, CFErrorRef
*error
)
3012 bool copyCloudAuthToken
= false;
3013 bool copyMobileMail
= false;
3015 bool copyPCS
= false;
3016 bool onlyDelete
= false;
3017 bool copyNSURLSesssion
= false;
3019 if (!client
->inMultiUser
)
3022 secnotice("syncbubble", "migration for uid %d uid for services %@", (int)uid
, services
);
3024 #if TARGET_OS_SIMULATOR
3027 if (uid
!= (uid_t
)client
->activeUser
)
3030 #error "no sync bubble on other platforms"
3034 * First select that services to copy/delete
3037 if (ArrayContains(services
, CFSTR("com.apple.bird.usermanager.sync"))
3038 || ArrayContains(services
, CFSTR("com.apple.cloudphotod.sync"))
3039 || ArrayContains(services
, CFSTR("com.apple.cloudphotod.syncstakeholder"))
3040 || ArrayContains(services
, CFSTR("com.apple.cloudd.usermanager.sync")))
3042 copyCloudAuthToken
= true;
3046 if (ArrayContains(services
, CFSTR("com.apple.nsurlsessiond.usermanager.sync")))
3048 copyCloudAuthToken
= true;
3049 copyNSURLSesssion
= true;
3052 if (ArrayContains(services
, CFSTR("com.apple.syncdefaultsd.usermanager.sync"))) {
3053 copyCloudAuthToken
= true;
3055 if (ArrayContains(services
, CFSTR("com.apple.mailq.sync")) || ArrayContains(services
, CFSTR("com.apple.mailq.sync.xpc"))) {
3056 copyCloudAuthToken
= true;
3057 copyMobileMail
= true;
3062 * The actually copy/delete the items selected
3065 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyPCS
, &inet_class
, PCSItems
, sizeof(PCSItems
)/sizeof(PCSItems
[0]), error
);
3067 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyPCS
, &genp_class
, PCSItems
, sizeof(PCSItems
)/sizeof(PCSItems
[0]), error
);
3071 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyMobileMail
, &genp_class
, MobileMailItems
, sizeof(MobileMailItems
)/sizeof(MobileMailItems
[0]), error
);
3075 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyCloudAuthToken
, &genp_class
, AccountsdItems
, sizeof(AccountsdItems
)/sizeof(AccountsdItems
[0]), error
);
3079 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyNSURLSesssion
, &inet_class
, NSURLSesssiond
, sizeof(NSURLSesssiond
)/sizeof(NSURLSesssiond
[0]), error
);
3087 * Migrate from user keychain to system keychain when switching to edu mode
3091 _SecServerTransmogrifyToSystemKeychain(SecurityClient
*client
, CFErrorRef
*error
)
3093 __block
bool ok
= true;
3096 * we are not in multi user yet, about to switch, otherwise we would
3097 * check that for client->inMultiuser here
3100 kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
3101 return kc_transaction(dbt
, error
, ^{
3102 CFDataRef systemUUID
= SecMUSRGetSystemKeychainUUID();
3104 const SecDbSchema
*newSchema
= kc_schemas
[0];
3105 SecDbClass
const *const *kcClass
;
3107 for (kcClass
= newSchema
->classes
; *kcClass
!= NULL
; kcClass
++) {
3108 CFErrorRef localError
= NULL
;
3111 if (*kcClass
== &tversion_class
|| *kcClass
== &identity_class
)
3114 q
= query_create(*kcClass
, SecMUSRGetSingleUserKeychainUUID(), NULL
, error
);
3118 ok
&= SecDbItemSelect(q
, dbt
, error
, ^bool(const SecDbAttr
*attr
) {
3119 return (attr
->flags
& kSecDbInFlag
) != 0;
3120 }, ^bool(const SecDbAttr
*attr
) {
3121 // No filtering please.
3123 }, ^bool(CFMutableStringRef sql
, bool *needWhere
) {
3124 SecDbAppendWhereOrAnd(sql
, needWhere
);
3125 CFStringAppendFormat(sql
, NULL
, CFSTR("musr = ?"));
3127 }, ^bool(sqlite3_stmt
*stmt
, int col
) {
3128 return SecDbBindObject(stmt
, col
++, SecMUSRGetSingleUserKeychainUUID(), error
);
3129 }, ^(SecDbItemRef item
, bool *stop
) {
3130 CFErrorRef localError
= NULL
;
3132 if (!SecDbItemSetValueWithName(item
, kSecAttrMultiUser
, systemUUID
, &localError
)) {
3133 secerror("item: %@ update musr to system failed: %@", item
, localError
);
3138 if (!SecDbItemDoUpdate(item
, item
, dbt
, &localError
, ^bool (const SecDbAttr
*attr
) {
3139 return attr
->kind
== kSecDbRowIdAttr
;
3141 secerror("item: %@ insert during UPDATE: %@", item
, localError
);
3147 SecErrorPropagate(localError
, error
);
3148 CFReleaseSafe(localError
);
3152 query_destroy(q
, &localError
);
3163 * Delete account from local usage
3167 _SecServerDeleteMUSERViews(SecurityClient
*client
, uid_t uid
, CFErrorRef
*error
)
3169 return kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
3170 CFDataRef musrView
= NULL
, syncBubbleView
= NULL
;
3173 syncBubbleView
= SecMUSRCreateSyncBubbleUserUUID(uid
);
3174 require(syncBubbleView
, fail
);
3176 musrView
= SecMUSRCreateActiveUserUUID(uid
);
3177 require(musrView
, fail
);
3179 require(ok
= SecServerDeleteAllForUser(dbt
, syncBubbleView
, false, error
), fail
);
3180 require(ok
= SecServerDeleteAllForUser(dbt
, musrView
, false, error
), fail
);
3183 CFReleaseNull(syncBubbleView
);
3184 CFReleaseNull(musrView
);
3190 #endif /* TARGET_OS_IOS */
3193 _SecServerGetKeyStats(const SecDbClass
*qclass
,
3194 struct _SecServerKeyStats
*stats
)
3196 __block CFErrorRef error
= NULL
;
3199 Query
*q
= query_create(qclass
, NULL
, NULL
, &error
);
3202 q
->q_return_type
= kSecReturnDataMask
| kSecReturnAttributesMask
;
3203 q
->q_limit
= kSecMatchUnlimited
;
3204 q
->q_keybag
= KEYBAG_DEVICE
;
3205 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleWhenUnlocked
, q
);
3206 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleAfterFirstUnlock
, q
);
3207 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleAlways
, q
);
3208 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleWhenUnlockedThisDeviceOnly
, q
);
3209 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
, q
);
3210 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleAlwaysThisDeviceOnly
, q
);
3211 query_add_attribute(kSecAttrTombstone
, kCFBooleanFalse
, q
);
3213 kc_with_dbt(false, &error
, ^(SecDbConnectionRef dbconn
) {
3214 CFErrorRef error2
= NULL
;
3215 __block CFIndex totalSize
= 0;
3216 stats
->maxDataSize
= 0;
3218 SecDbItemSelect(q
, dbconn
, &error2
, NULL
, ^bool(const SecDbAttr
*attr
) {
3219 return CFDictionaryContainsKey(q
->q_item
, attr
->name
);
3220 }, NULL
, NULL
, ^(SecDbItemRef item
, bool *stop
) {
3221 CFErrorRef error3
= NULL
;
3222 CFDataRef data
= SecDbItemGetValue(item
, &v6v_Data
, &error3
);
3224 CFIndex size
= CFDataGetLength(data
);
3225 if (size
> stats
->maxDataSize
)
3226 stats
->maxDataSize
= size
;
3230 CFReleaseNull(error3
);
3232 CFReleaseNull(error2
);
3234 stats
->averageSize
= totalSize
/ stats
->items
;
3243 CFReleaseNull(error
);
3245 query_destroy(q
, NULL
);