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>
47 // TODO: Make this include work on both platforms. rdar://problem/16526848
48 #if TARGET_OS_EMBEDDED
49 #include <Security/SecEntitlements.h>
50 #include <MobileKeyBag/MobileKeyBag.h>
52 /* defines from <Security/SecEntitlements.h> */
53 #define kSecEntitlementAssociatedDomains CFSTR("com.apple.developer.associated-domains")
54 #define kSecEntitlementPrivateAssociatedDomains CFSTR("com.apple.private.associated-domains")
57 #if TARGET_OS_EMBEDDED
58 #include <AggregateDictionary/ADClient.h>
63 #include <utilities/array_size.h>
64 #include <utilities/SecFileLocations.h>
65 #include <utilities/SecTrace.h>
66 #include <utilities/SecXPCError.h>
67 #include <Security/SecuritydXPC.h>
68 #include "swcagent_client.h"
70 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
72 #include <SharedWebCredentials/SharedWebCredentials.h>
74 typedef OSStatus (*SWCCheckService_f
)(CFStringRef service
, CFStringRef appID
, CFStringRef domain
, SWCCheckServiceCompletion_b completion
);
75 typedef OSStatus (*SWCSetServiceFlags_f
)(CFStringRef service
, CFStringRef appID
, CFStringRef domain
, SWCFlags mask
, SWCFlags flags
, SWCSetServiceFlagsCompletion_b completion
);
77 typedef uint32_t SWCFlags
;
78 #define kSWCFlags_None 0
79 #define kSWCFlag_Pending ( 1U << 0 )
80 #define kSWCFlag_SiteApproved ( 1U << 1 )
81 #define kSWCFlag_SiteDenied ( 1U << 2 )
82 #define kSWCFlag_UserApproved ( 1U << 3 )
83 #define kSWCFlag_UserDenied ( 1U << 4 )
84 #define kSWCFlag_ExternalMask ( kSWCFlag_UserApproved | kSWCFlag_UserDenied )
87 /* Changed the name of the keychain changed notification, for testing */
88 static const char *g_keychain_changed_notification
= kSecServerKeychainChangedNotification
;
90 void SecItemServerSetKeychainChangedNotification(const char *notification_name
)
92 g_keychain_changed_notification
= notification_name
;
95 void SecKeychainChanged(bool syncWithPeers
) {
96 uint32_t result
= notify_post(g_keychain_changed_notification
);
98 SOSCCSyncWithAllPeers();
99 if (result
== NOTIFY_STATUS_OK
)
100 secnotice("item", "Sent %s%s", syncWithPeers
? "SyncWithAllPeers and " : "", g_keychain_changed_notification
);
102 secerror("%snotify_post %s returned: %" PRIu32
, syncWithPeers
? "Sent SyncWithAllPeers, " : "", g_keychain_changed_notification
, result
);
105 /* Return the current database version in *version. */
106 static bool SecKeychainDbGetVersion(SecDbConnectionRef dbt
, int *version
, CFErrorRef
*error
)
108 __block
bool ok
= true;
109 __block CFErrorRef localError
= NULL
;
110 __block
bool found
= false;
113 * First check for the version table itself
116 ok
&= SecDbPrepare(dbt
, CFSTR("SELECT name FROM sqlite_master WHERE type='table' AND name='tversion'"), &localError
, ^(sqlite3_stmt
*stmt
) {
117 ok
= SecDbStep(dbt
, stmt
, NULL
, ^(bool *stop
) {
122 require_action(ok
, out
, SecDbError(SQLITE_CORRUPT
, error
, CFSTR("Failed to read sqlite_master table: %@"), localError
));
124 secnotice("upgr", "no tversion table, will setup a new database: %@", localError
);
130 * Now build up major.minor
133 ok
&= SecDbPrepare(dbt
, CFSTR("SELECT version FROM tversion"), &localError
, ^(sqlite3_stmt
*stmt
) {
134 ok
= SecDbStep(dbt
, stmt
, NULL
, ^(bool *stop
) {
135 *version
= sqlite3_column_int(stmt
, 0);
140 if (ok
&& (*version
& 0xffff) >= 9) {
141 ok
&= SecDbPrepare(dbt
, CFSTR("SELECT minor FROM tversion WHERE version = ?"), &localError
, ^(sqlite3_stmt
*stmt
) {
142 ok
= SecDbBindInt(stmt
, 1, *version
, &localError
) &&
143 SecDbStep(dbt
, stmt
, NULL
, ^(bool *stop
) {
144 int64_t minor
= sqlite3_column_int64(stmt
, 0);
145 *version
|= ((minor
& 0xff) << 8) | ((minor
& 0xff0000) << 8);
152 secnotice("upgr", "database version is: 0x%08x : %d : %@", *version
, ok
, localError
);
153 CFReleaseSafe(localError
);
160 isClassD(SecDbItemRef item
)
162 CFTypeRef accessible
= SecDbItemGetCachedValueWithName(item
, kSecAttrAccessible
);
164 if (CFEqualSafe(accessible
, kSecAttrAccessibleAlways
) || CFEqualSafe(accessible
, kSecAttrAccessibleAlwaysThisDeviceOnly
))
169 #if TARGET_OS_EMBEDDED
172 measureDuration(struct timeval
*start
)
177 gettimeofday(&stop
, NULL
);
179 duration
= (stop
.tv_sec
-start
->tv_sec
) * 1000;
180 duration
+= (stop
.tv_usec
/ 1000) - (start
->tv_usec
/ 1000);
182 return SecBucket2Significant(duration
);
186 measureUpgradePhase1(struct timeval
*start
, bool success
, int64_t itemsMigrated
)
188 int64_t duration
= measureDuration(start
);
191 ADClientSetValueForScalarKey(CFSTR("com.apple.keychain.phase1.migrated-items-success"), itemsMigrated
);
192 ADClientSetValueForScalarKey(CFSTR("com.apple.keychain.phase1.migrated-time-success"), duration
);
194 ADClientSetValueForScalarKey(CFSTR("com.apple.keychain.phase1.migrated-items-fail"), itemsMigrated
);
195 ADClientSetValueForScalarKey(CFSTR("com.apple.keychain.phase1.migrated-time-fail"), duration
);
200 measureUpgradePhase2(struct timeval
*start
, int64_t itemsMigrated
)
202 int64_t duration
= measureDuration(start
);
204 ADClientSetValueForScalarKey(CFSTR("com.apple.keychain.phase2.migrated-items"), itemsMigrated
);
205 ADClientSetValueForScalarKey(CFSTR("com.apple.keychain.phase2.migrated-time"), duration
);
207 #endif /* TARGET_OS_EMBEDDED */
209 // Goes through all tables represented by old_schema and tries to migrate all items from them into new (current version) tables.
210 static bool UpgradeSchemaPhase1(SecDbConnectionRef dbt
, const SecDbSchema
*oldSchema
, CFErrorRef
*error
)
212 __block
bool ok
= true;
213 const SecDbSchema
*newSchema
= kc_schemas
[0];
214 SecDbClass
const *const *oldClass
;
215 SecDbClass
const *const *newClass
;
216 SecDbQueryRef query
= NULL
;
217 CFMutableStringRef sql
= NULL
;
218 #if TARGET_OS_EMBEDDED
219 __block
int64_t itemsMigrated
= 0;
220 struct timeval start
;
222 gettimeofday(&start
, NULL
);
225 // Rename existing tables to old names, as present in old schemas.
226 sql
= CFStringCreateMutable(NULL
, 0);
227 for (oldClass
= oldSchema
->classes
, newClass
= newSchema
->classes
;
228 *oldClass
!= NULL
&& *newClass
!= NULL
; oldClass
++, newClass
++) {
229 if (!CFEqual((*oldClass
)->name
, (*newClass
)->name
)) {
230 CFStringAppendFormat(sql
, NULL
, CFSTR("ALTER TABLE %@ RENAME TO %@;"),
231 (*newClass
)->name
, (*oldClass
)->name
);
233 CFStringAppendFormat(sql
, NULL
, CFSTR("DROP TABLE %@;"), (*oldClass
)->name
);
236 require_quiet(ok
&= SecDbExec(dbt
, sql
, error
), out
);
239 // Drop indices that that new schemas will use
240 sql
= CFStringCreateMutable(NULL
, 0);
241 for (newClass
= newSchema
->classes
; *newClass
!= NULL
; newClass
++) {
242 SecDbForEachAttrWithMask((*newClass
),desc
, kSecDbIndexFlag
| kSecDbInFlag
) {
243 CFStringAppendFormat(sql
, 0, CFSTR("DROP INDEX IF EXISTS %@%@;"), (*newClass
)->name
, desc
->name
);
246 require_quiet(ok
&= SecDbExec(dbt
, sql
, error
), out
);
249 // Create tables for new schema.
250 require_quiet(ok
&= SecItemDbCreateSchema(dbt
, newSchema
, false, error
), out
);
251 // Go through all classes of current schema to transfer all items to new tables.
252 for (oldClass
= oldSchema
->classes
, newClass
= newSchema
->classes
;
253 *oldClass
!= NULL
&& *newClass
!= NULL
; oldClass
++, newClass
++) {
254 if (CFEqual((*oldClass
)->name
, (*newClass
)->name
))
257 secnotice("upgr", "Upgrading table %@", (*oldClass
)->name
);
259 // Prepare query to iterate through all items in cur_class.
261 query_destroy(query
, NULL
);
262 require_quiet(query
= query_create(*oldClass
, SecMUSRGetAllViews(), NULL
, error
), out
);
264 ok
&= SecDbItemSelect(query
, dbt
, error
, ^bool(const SecDbAttr
*attr
) {
265 // We are interested in all attributes which are physically present in the DB.
266 return (attr
->flags
& kSecDbInFlag
) != 0;
267 }, ^bool(const SecDbAttr
*attr
) {
268 // No filtering please.
270 }, NULL
, NULL
, ^(SecDbItemRef item
, bool *stop
) {
271 CFErrorRef localError
= NULL
;
273 #if TARGET_OS_EMBEDDED
276 // Switch item to the new class.
277 item
->class = *newClass
;
279 if (isClassD(item
)) {
281 ok
&= SecDbItemEnsureDecrypted(item
, &localError
);
282 require_quiet(ok
, out
);
284 // Delete SHA1 field from the item, so that it is newly recalculated before storing
285 // the item into the new table.
286 require_quiet(ok
&= SecDbItemSetValue(item
, SecDbClassAttrWithKind(item
->class, kSecDbSHA1Attr
, error
),
287 kCFNull
, error
), out
);
289 // Leave item encrypted, do not ever try to decrypt it since it will fail.
290 item
->_edataState
= kSecDbItemAlwaysEncrypted
;
292 // Insert new item into the new table.
293 if (!SecDbItemInsert(item
, dbt
, &localError
)) {
294 secerror("item: %@ insert during upgrade: %@", item
, localError
);
300 OSStatus status
= SecErrorGetOSStatus(localError
);
303 // continue to upgrade and don't propagate errors for insert failures
304 // that are typical of a single item failure
306 case errSecDuplicateItem
:
309 case errSecInteractionNotAllowed
:
310 case errSecAuthNeeded
:
314 ok
&= CFErrorPropagate(CFRetainSafe(localError
), error
);
317 CFReleaseSafe(localError
);
322 require_quiet(ok
, out
);
325 // Remove old tables from the DB.
326 sql
= CFStringCreateMutable(NULL
, 0);
327 for (oldClass
= oldSchema
->classes
, newClass
= newSchema
->classes
;
328 *oldClass
!= NULL
&& *newClass
!= NULL
; oldClass
++, newClass
++) {
329 if (!CFEqual((*oldClass
)->name
, (*newClass
)->name
)) {
330 CFStringAppendFormat(sql
, NULL
, CFSTR("DROP TABLE %@;"), (*oldClass
)->name
);
333 require_quiet(ok
&= SecDbExec(dbt
, sql
, error
), out
);
336 #if TARGET_OS_EMBEDDED
337 measureUpgradePhase1(&start
, ok
, SecBucket2Significant(itemsMigrated
));
341 query_destroy(query
, NULL
);
347 // Goes through all tables represented by old_schema and tries to migrate all items from them into new (current version) tables.
348 static bool UpgradeItemPhase2(SecDbConnectionRef dbt
, bool *inProgress
, CFErrorRef
*error
) {
349 __block
bool ok
= true;
350 SecDbQueryRef query
= NULL
;
351 #if TARGET_OS_EMBEDDED
352 __block
int64_t itemsMigrated
= 0;
353 struct timeval start
;
355 gettimeofday(&start
, NULL
);
358 // Go through all classes in new schema
359 const SecDbSchema
*newSchema
= kc_schemas
[0];
360 for (const SecDbClass
*const *class = newSchema
->classes
; *class != NULL
&& !*inProgress
; class++) {
361 if(CFEqual((*class)->name
, tversion_class
.name
)) {
362 //Don't try to decrypt items in tversion table
366 const SecDbAttr
*pdmn
= SecDbClassAttrWithKind(*class, kSecDbAccessAttr
, error
);
371 // Prepare query to go through all non-DK|DKU items
373 query_destroy(query
, NULL
);
375 require_action_quiet(query
= query_create(*class, SecMUSRGetAllViews(), NULL
, error
), out
, ok
= false);
376 ok
= SecDbItemSelect(query
, dbt
, error
, NULL
, ^bool(const SecDbAttr
*attr
) {
377 // No simple per-attribute filtering.
379 }, ^bool(CFMutableStringRef sql
, bool *needWhere
) {
380 // Select only non-D-class items
381 SecDbAppendWhereOrAnd(sql
, needWhere
);
382 CFStringAppendFormat(sql
, NULL
, CFSTR("NOT %@ IN (?,?)"), pdmn
->name
);
384 }, ^bool(sqlite3_stmt
*stmt
, int col
) {
385 return SecDbBindObject(stmt
, col
++, kSecAttrAccessibleAlwaysPrivate
, error
) &&
386 SecDbBindObject(stmt
, col
++, kSecAttrAccessibleAlwaysThisDeviceOnlyPrivate
, error
);
387 }, ^(SecDbItemRef item
, bool *stop
) {
388 CFErrorRef localError
= NULL
;
390 #if TARGET_OS_EMBEDDED
395 if (SecDbItemEnsureDecrypted(item
, &localError
)) {
397 // Delete SHA1 field from the item, so that it is newly recalculated before storing
398 // the item into the new table.
399 require_quiet(ok
= SecDbItemSetValue(item
, SecDbClassAttrWithKind(item
->class, kSecDbSHA1Attr
, error
),
400 kCFNull
, &localError
), out
);
402 // Replace item with the new value in the table; this will cause the item to be decoded and recoded back,
403 // incl. recalculation of item's hash.
404 ok
= SecDbItemUpdate(item
, item
, dbt
, false, &localError
);
408 CFIndex status
= CFErrorGetCode(localError
);
412 // Items producing errSecDecode are silently dropped - they are not decodable and lost forever.
413 (void)SecDbItemDelete(item
, dbt
, false, error
);
416 case errSecInteractionNotAllowed
:
417 // If we are still not able to decrypt the item because the class key is not released yet,
418 // remember that DB still needs phase2 migration to be run next time a connection is made. Also
419 // stop iterating next items, it would be just waste of time because the whole iteration will be run
420 // next time when this phase2 will be rerun.
425 case errSecAuthNeeded
:
426 // errSecAuthNeeded means that it is an ACL-based item which requires authentication (or at least
427 // ACM context, which we do not have).
431 // Other errors should abort the migration completely.
432 ok
= CFErrorPropagate(CFRetainSafe(localError
), error
);
438 CFReleaseSafe(localError
);
439 *stop
= *stop
|| !ok
;
445 #if TARGET_OS_EMBEDDED
446 measureUpgradePhase2(&start
, SecBucket2Significant(itemsMigrated
));
451 query_destroy(query
, NULL
);
455 #define SCHEMA_VERSION(schema) ((((schema)->minorVersion) << 8) | ((schema)->majorVersion))
456 #define VERSION_MAJOR(version) ((version) & 0xff)
457 #define VERSION_MINOR(version) (((version) >> 8) & 0xff)
458 #define VERSION_NEW(version) ((version) & 0xffff)
459 #define VERSION_OLD(version) (((version) >> 16) & 0xffff)
461 static bool SecKeychainDbUpgradeFromVersion(SecDbConnectionRef dbt
, int version
, bool *inProgress
, CFErrorRef
*error
) {
462 __block
bool didPhase2
= false;
463 __block
bool ok
= true;
464 __block CFErrorRef localError
= NULL
;
469 // The schema we want to have is the first in the list of schemas.
470 const SecDbSchema
*newSchema
= kc_schemas
[0];
472 // If DB schema is the one we want, we are done.
473 require_quiet(SCHEMA_VERSION(newSchema
) != version
, out
);
475 // Check if the schema of the database on disk is the same major, but newer version then what we have
476 // in code, lets just skip this since a newer version of the OS have upgrade it. Since its the same
477 // major, its a promise that it will be compatible.
478 if (newSchema
->majorVersion
== VERSION_MAJOR(version
) && newSchema
->minorVersion
< VERSION_MINOR(version
)) {
479 secnotice("upgr", "skipping upgrade since minor is newer");
483 if (VERSION_MAJOR(version
) < 6) {
484 // Pre v6 keychains need to have WAL enabled, since SecDb only does this at db creation time.
485 // NOTE: This has to be run outside of a transaction.
486 require_action_quiet(ok
= (SecDbExec(dbt
, CFSTR("PRAGMA auto_vacuum = FULL"), &localError
) &&
487 SecDbExec(dbt
, CFSTR("PRAGMA journal_mode = WAL"), &localError
)),
488 out
, secerror("unable to enable WAL or auto vacuum, marking DB as corrupt: %@",
492 ok
&= SecDbTransaction(dbt
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
493 CFStringRef sql
= NULL
;
494 bool didPhase1
= false;
496 // Get version again once we start a transaction, someone else might change the migration state.
498 require_quiet(ok
= SecKeychainDbGetVersion(dbt
, &version2
, &localError
), out
);
499 // Check if someone has raced us to the migration of the database
500 require_action(version
== version2
, out
, CFReleaseNull(localError
); ok
= true);
502 require_quiet(SCHEMA_VERSION(newSchema
) != version2
, out
);
504 // If this is empty database, just create table according to schema and be done with it.
505 require_action_quiet(version2
!= 0, out
, ok
= SecItemDbCreateSchema(dbt
, newSchema
, true, &localError
));
507 int oldVersion
= VERSION_OLD(version2
);
508 version2
= VERSION_NEW(version2
);
510 require_action_quiet(version2
== SCHEMA_VERSION(newSchema
) || oldVersion
== 0, out
,
511 ok
= SecDbError(SQLITE_CORRUPT
, &localError
,
512 CFSTR("Half migrated but obsolete DB found: found 0x%x(0x%x) but 0x%x is needed"),
513 version2
, oldVersion
, SCHEMA_VERSION(newSchema
)));
515 // Check whether we have both old and new tables in the DB.
516 if (oldVersion
== 0) {
517 // Pure old-schema migration attempt, with full blown table renames etc (a.k.a. phase1)
518 oldVersion
= version2
;
519 version2
= SCHEMA_VERSION(newSchema
);
521 // Find schema for old database.
522 const SecDbSchema
*oldSchema
= NULL
;
523 for (const SecDbSchema
* const *pschema
= kc_schemas
; *pschema
; ++pschema
) {
524 if (SCHEMA_VERSION((*pschema
)) == oldVersion
) {
525 oldSchema
= *pschema
;
530 // If we are attempting to upgrade from a version for which we have no schema, fail.
531 require_action_quiet(oldSchema
!= NULL
, out
,
532 ok
= SecDbError(SQLITE_CORRUPT
, &localError
, CFSTR("no schema for version: 0x%x"), oldVersion
);
533 secerror("no schema for version 0x%x", oldVersion
));
535 secnotice("upgr", "Upgrading from version 0x%x to 0x%x", oldVersion
, SCHEMA_VERSION(newSchema
));
536 require(ok
= UpgradeSchemaPhase1(dbt
, oldSchema
, &localError
), out
);
542 CFErrorRef phase2Error
= NULL
;
544 // Lests try to go through non-D-class items in new tables and apply decode/encode on them
545 // If this fails the error will be ignored after doing a phase1 since but not in the second
546 // time when we are doing phase2.
547 ok
= UpgradeItemPhase2(dbt
, inProgress
, &phase2Error
);
553 SecErrorPropagate(phase2Error
, &localError
);
556 CFReleaseNull(phase2Error
);
560 // If either migration path we did reported that the migration was complete, signalize that
561 // in the version database by cleaning oldVersion (which is stored in upper halfword of the version)
562 secnotice("upgr", "Done upgrading from version 0x%x to 0x%x", oldVersion
, SCHEMA_VERSION(newSchema
));
569 // Update database version table.
570 uint32_t major
= (VERSION_MAJOR(version2
)) | (VERSION_MAJOR(oldVersion
) << 16);
571 uint32_t minor
= (VERSION_MINOR(version2
)) | (VERSION_MINOR(oldVersion
) << 16);
572 secnotice("upgr", "Upgrading saving version major 0x%x minor 0x%x", major
, minor
);
573 sql
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("UPDATE tversion SET version='%d', minor='%d'"),
575 require_quiet(ok
= SecDbExec(dbt
, sql
, &localError
), out
);
582 if (ok
&& didPhase2
) {
583 #if TARGET_OS_EMBEDDED
584 ADClientAddValueForScalarKey(CFSTR("com.apple.keychain.migration-success"), 1);
589 if (!ok
|| localError
) {
591 * We assume that database is corrupt at this point, but we need to
592 * check if the error we got isn't severe enough to mark the database as corrupt.
593 * In those cases we opt out of corrupting the database.
595 bool markedCorrupt
= true;
598 secwarning("upgrade: error has been set but status is true");
602 CFStringRef domain
= CFErrorGetDomain(localError
);
603 CFIndex code
= CFErrorGetCode(localError
);
605 if (CFEqualSafe(domain
, kSecDbErrorDomain
) &&
606 ((code
& 0xff) == SQLITE_LOCKED
|| (code
& 0xff) == SQLITE_BUSY
))
608 /* sqlite just busy doing something else, lets try upgrading some other time */
610 markedCorrupt
= false;
611 CFReleaseNull(localError
);
613 secerror("unable to complete upgrade, marking DB as corrupt: %@", localError
);
616 secerror("unable to complete upgrade and no error object returned, marking DB as corrupt");
619 SecDbCorrupt(dbt
, localError
);
620 #if TARGET_OS_EMBEDDED
621 ADClientAddValueForScalarKey(CFSTR("com.apple.keychain.migration-failure"), 1);
627 *error
= (CFErrorRef
)CFRetain(localError
);
629 CFReleaseNull(localError
);
635 /* AUDIT[securityd](done):
636 accessGroup (ok) is a caller provided, non NULL CFTypeRef.
638 Return true iff accessGroup is allowable according to accessGroups.
640 static bool accessGroupsAllows(CFArrayRef accessGroups
,
641 CFStringRef accessGroup
) {
642 /* NULL accessGroups is wildcard. */
645 /* Make sure we have a string. */
646 if (!isString(accessGroup
))
649 /* Having the special accessGroup "*" allows access to all accessGroups. */
650 CFRange range
= { 0, CFArrayGetCount(accessGroups
) };
652 (CFArrayContainsValue(accessGroups
, range
, accessGroup
) ||
653 CFArrayContainsValue(accessGroups
, range
, CFSTR("*"))))
659 bool itemInAccessGroup(CFDictionaryRef item
, CFArrayRef accessGroups
) {
660 return accessGroupsAllows(accessGroups
,
661 CFDictionaryGetValue(item
, kSecAttrAccessGroup
));
665 static CF_RETURNS_RETAINED CFDataRef
SecServerExportBackupableKeychain(SecDbConnectionRef dbt
,
666 SecurityClient
*client
,
667 keybag_handle_t src_keybag
, keybag_handle_t dest_keybag
, CFErrorRef
*error
) {
668 CFDataRef data_out
= NULL
;
669 /* Export everything except the items for which SecItemIsSystemBound()
671 CFDictionaryRef keychain
= SecServerCopyKeychainPlist(dbt
, client
,
672 src_keybag
, dest_keybag
, kSecBackupableItemFilter
,
675 data_out
= CFPropertyListCreateData(kCFAllocatorDefault
, keychain
,
676 kCFPropertyListBinaryFormat_v1_0
,
684 static bool SecServerImportBackupableKeychain(SecDbConnectionRef dbt
,
685 SecurityClient
*client
,
686 keybag_handle_t src_keybag
,
687 keybag_handle_t dest_keybag
,
691 return kc_transaction(dbt
, error
, ^{
693 CFDictionaryRef keychain
;
694 keychain
= CFPropertyListCreateWithData(kCFAllocatorDefault
, data
,
695 kCFPropertyListImmutable
, NULL
,
698 if (isDictionary(keychain
)) {
699 ok
= SecServerImportKeychainInPlist(dbt
,
704 kSecBackupableItemFilter
,
707 ok
= SecError(errSecParam
, error
, CFSTR("import: keychain is not a dictionary"));
715 static CFDataRef
SecServerKeychainCreateBackup(SecDbConnectionRef dbt
, SecurityClient
*client
, CFDataRef keybag
,
716 CFDataRef password
, CFErrorRef
*error
) {
717 CFDataRef backup
= NULL
;
718 keybag_handle_t backup_keybag
;
719 if (ks_open_keybag(keybag
, password
, &backup_keybag
, error
)) {
720 /* Export from system keybag to backup keybag. */
721 backup
= SecServerExportBackupableKeychain(dbt
, client
, KEYBAG_DEVICE
, backup_keybag
, error
);
722 if (!ks_close_keybag(backup_keybag
, error
)) {
723 CFReleaseNull(backup
);
729 static bool SecServerKeychainRestore(SecDbConnectionRef dbt
,
730 SecurityClient
*client
,
736 keybag_handle_t backup_keybag
;
737 if (!ks_open_keybag(keybag
, password
, &backup_keybag
, error
))
740 /* Import from backup keybag to system keybag. */
741 bool ok
= SecServerImportBackupableKeychain(dbt
, client
, backup_keybag
, KEYBAG_DEVICE
,
743 ok
&= ks_close_keybag(backup_keybag
, error
);
749 // MARK - External SPI support code.
751 CFStringRef
__SecKeychainCopyPath(void) {
752 CFStringRef kcRelPath
= NULL
;
754 kcRelPath
= CFSTR("keychain-2.db");
756 kcRelPath
= CFSTR("keychain-2-debug.db");
759 CFStringRef kcPath
= NULL
;
760 CFURLRef kcURL
= SecCopyURLForFileInKeychainDirectory(kcRelPath
);
762 kcPath
= CFURLCopyFileSystemPath(kcURL
, kCFURLPOSIXPathStyle
);
769 // MARK: kc_dbhandle init and reset
771 SecDbRef
SecKeychainDbCreate(CFStringRef path
) {
772 return SecDbCreate(path
, ^bool (SecDbConnectionRef dbconn
, bool didCreate
, bool *callMeAgainForNextConnection
, CFErrorRef
*error
) {
773 // Upgrade from version 0 means create the schema in empty db.
777 ok
= SecKeychainDbGetVersion(dbconn
, &version
, error
);
779 ok
= ok
&& SecKeychainDbUpgradeFromVersion(dbconn
, version
, callMeAgainForNextConnection
, error
);
781 secerror("Upgrade %sfailed: %@", didCreate
? "from v0 " : "", error
? *error
: NULL
);
787 static SecDbRef _kc_dbhandle
= NULL
;
789 static void kc_dbhandle_init(void) {
790 SecDbRef oldHandle
= _kc_dbhandle
;
792 CFStringRef dbPath
= __SecKeychainCopyPath();
794 _kc_dbhandle
= SecKeychainDbCreate(dbPath
);
797 secerror("no keychain path available");
800 secerror("replaced %@ with %@", oldHandle
, _kc_dbhandle
);
801 CFRelease(oldHandle
);
805 // A callback for the sqlite3_log() interface.
806 static void sqlite3Log(void *pArg
, int iErrCode
, const char *zMsg
){
807 secdebug("sqlite3", "(%d) %s", iErrCode
, zMsg
);
811 _SecServerDatabaseSetup(void)
813 static dispatch_once_t onceToken
;
814 dispatch_once(&onceToken
, ^{
815 int rx
= sqlite3_config(SQLITE_CONFIG_LOG
, sqlite3Log
, NULL
);
816 if (SQLITE_OK
!= rx
) {
817 secwarning("Could not set up sqlite global error logging to syslog: %d", rx
);
822 static SecDbRef
kc_dbhandle(void)
824 static dispatch_once_t onceToken
;
825 dispatch_once(&onceToken
, ^{
826 _SecServerDatabaseSetup();
832 /* For whitebox testing only */
833 void SecKeychainDbReset(dispatch_block_t inbetween
)
835 CFStringRef dbPath
= __SecKeychainCopyPath();
839 CFReleaseNull(_kc_dbhandle
);
844 _kc_dbhandle
= SecKeychainDbCreate(dbPath
);
848 static SecDbConnectionRef
kc_aquire_dbt(bool writeAndRead
, CFErrorRef
*error
) {
849 SecDbRef db
= kc_dbhandle();
851 SecError(errSecDataNotAvailable
, error
, CFSTR("failed to get a db handle"));
854 return SecDbConnectionAquire(db
, !writeAndRead
, error
);
857 /* Return a per thread dbt handle for the keychain. If create is true create
858 the database if it does not yet exist. If it is false, just return an
859 error if it fails to auto-create. */
860 static bool kc_with_dbt(bool writeAndRead
, CFErrorRef
*error
, bool (^perform
)(SecDbConnectionRef dbt
))
862 // Make sure we initialize our engines before writing to the keychain
864 SecItemDataSourceFactoryGetDefault();
867 SecDbConnectionRef dbt
= kc_aquire_dbt(writeAndRead
, error
);
870 SecDbConnectionRelease(dbt
);
876 items_matching_issuer_parent(SecDbConnectionRef dbt
, CFArrayRef accessGroups
, CFDataRef musrView
,
877 CFDataRef issuer
, CFArrayRef issuers
, int recurse
)
880 CFArrayRef results
= NULL
;
884 if (CFArrayContainsValue(issuers
, CFRangeMake(0, CFArrayGetCount(issuers
)), issuer
))
887 /* XXX make musr supported */
888 const void *keys
[] = { kSecClass
, kSecReturnRef
, kSecAttrSubject
};
889 const void *vals
[] = { kSecClassCertificate
, kCFBooleanTrue
, issuer
};
890 CFDictionaryRef query
= CFDictionaryCreate(kCFAllocatorDefault
, keys
, vals
, array_size(keys
), NULL
, NULL
);
895 CFErrorRef localError
= NULL
;
896 q
= query_create_with_limit(query
, musrView
, kSecMatchUnlimited
, &localError
);
899 s3dl_copy_matching(dbt
, q
, (CFTypeRef
*)&results
, accessGroups
, &localError
);
900 query_destroy(q
, &localError
);
903 secerror("items matching issuer parent: %@", localError
);
904 CFReleaseNull(localError
);
908 count
= CFArrayGetCount(results
);
909 for (i
= 0; (i
< count
) && !found
; i
++) {
910 CFDictionaryRef cert_dict
= (CFDictionaryRef
)CFArrayGetValueAtIndex(results
, i
);
911 CFDataRef cert_issuer
= CFDictionaryGetValue(cert_dict
, kSecAttrIssuer
);
912 if (CFEqual(cert_issuer
, issuer
))
915 found
= items_matching_issuer_parent(dbt
, accessGroups
, musrView
, cert_issuer
, issuers
, recurse
);
917 CFReleaseSafe(results
);
923 _FilterWithPolicy(SecPolicyRef policy
, CFDateRef date
, SecCertificateRef cert
)
925 CFDictionaryRef props
= NULL
;
926 CFArrayRef keychains
= NULL
;
927 CFArrayRef anchors
= NULL
;
928 CFArrayRef certs
= NULL
;
929 CFArrayRef chain
= NULL
;
930 SecTrustRef trust
= NULL
;
932 SecTrustResultType trustResult
;
933 Boolean needChain
= false;
934 __block
bool ok
= false;
936 if (!policy
|| !cert
) return false;
938 certs
= CFArrayCreate(NULL
, (const void **)&cert
, (CFIndex
)1, &kCFTypeArrayCallBacks
);
939 require_noerr_quiet(SecTrustCreateWithCertificates(certs
, policy
, &trust
), cleanup
);
941 /* Set evaluation date, if specified (otherwise current date is implied) */
942 if (date
&& (CFGetTypeID(date
) == CFDateGetTypeID())) {
943 require_noerr_quiet(SecTrustSetVerifyDate(trust
, date
), cleanup
);
946 /* Check whether this is the X509 Basic policy, which means chain building */
947 props
= SecPolicyCopyProperties(policy
);
949 CFTypeRef oid
= (CFTypeRef
) CFDictionaryGetValue(props
, kSecPolicyOid
);
950 if (oid
&& (CFEqual(oid
, kSecPolicyAppleX509Basic
) ||
951 CFEqual(oid
, kSecPolicyAppleRevocation
))) {
957 require_noerr_quiet(SecTrustEvaluateLeafOnly(trust
, &trustResult
), cleanup
);
959 require_noerr_quiet(SecTrustEvaluate(trust
, &trustResult
), cleanup
);
962 require_quiet((trustResult
== kSecTrustResultProceed
||
963 trustResult
== kSecTrustResultUnspecified
||
964 trustResult
== kSecTrustResultRecoverableTrustFailure
), cleanup
);
968 CFArrayRef properties
= SecTrustCopyProperties(trust
);
970 CFArrayRef properties
= SecTrustCopyProperties_ios(trust
);
973 CFArrayForEach(properties
, ^(const void *property
) {
974 CFDictionaryForEach((CFDictionaryRef
)property
, ^(const void *key
, const void *value
) {
975 if (CFEqual((CFTypeRef
)key
, kSecPropertyKeyType
) && CFEqual((CFTypeRef
)value
, kSecPropertyTypeError
))
979 CFRelease(properties
);
983 if(props
) CFRelease(props
);
984 if(chain
) CFRelease(chain
);
985 if(anchors
) CFRelease(anchors
);
986 if(keychains
) CFRelease(keychains
);
987 if(certs
) CFRelease(certs
);
988 if(trust
) CFRelease(trust
);
994 _FilterWithDate(CFDateRef validOnDate
, SecCertificateRef cert
)
996 if (!validOnDate
|| !cert
) return false;
998 CFAbsoluteTime at
, nb
, na
;
999 at
= CFDateGetAbsoluteTime((CFDateRef
)validOnDate
);
1002 nb
= SecCertificateNotValidBefore(cert
);
1003 na
= SecCertificateNotValidAfter(cert
);
1005 if (nb
== 0 || na
== 0 || nb
== na
) {
1007 secnotice("FilterWithDate", "certificate cannot operate");
1011 secnotice("FilterWithDate", "certificate is not valid yet");
1015 secnotice("FilterWithDate", "certificate expired");
1022 _FilterWithTrust(Boolean trustedOnly
, SecCertificateRef cert
)
1024 if (!cert
) return false;
1025 if (!trustedOnly
) return true;
1028 CFArrayRef certArray
= CFArrayCreate(NULL
, (const void**)&cert
, 1, &kCFTypeArrayCallBacks
);
1029 SecTrustRef trust
= NULL
;
1030 SecPolicyRef policy
= SecPolicyCreateBasicX509();
1031 require_quiet(policy
, out
);
1033 require_noerr_quiet(SecTrustCreateWithCertificates(certArray
, policy
, &trust
), out
);
1034 SecTrustResultType trustResult
;
1035 require_noerr_quiet(SecTrustEvaluate(trust
, &trustResult
), out
);
1037 require_quiet((trustResult
== kSecTrustResultProceed
||
1038 trustResult
== kSecTrustResultUnspecified
), out
);
1041 CFReleaseSafe(trust
);
1042 CFReleaseSafe(policy
);
1043 CFReleaseSafe(certArray
);
1047 static SecCertificateRef
1048 CopyCertificateFromItem(Query
*q
, CFDictionaryRef item
) {
1049 SecCertificateRef certRef
= NULL
;
1051 CFTypeRef tokenID
= NULL
;
1052 CFDataRef certData
= NULL
;
1053 if (q
->q_class
== &identity_class
) {
1054 certData
= CFDictionaryGetValue(item
, kSecAttrIdentityCertificateData
);
1055 tokenID
= CFDictionaryGetValue(item
, kSecAttrIdentityCertificateTokenID
);
1056 } else if (q
->q_class
== &cert_class
) {
1057 certData
= CFDictionaryGetValue(item
, kSecValueData
);
1058 tokenID
= CFDictionaryGetValue(item
, kSecAttrTokenID
);
1061 require_quiet(certData
, out
);
1062 if (tokenID
!= NULL
) {
1063 CFErrorRef error
= NULL
;
1064 CFDataRef tokenCertData
= _SecTokenItemCopyValueData(certData
, &error
);
1065 require_action_quiet(tokenCertData
, out
, { secerror("function _SecTokenItemCopyValueData failed with: %@", error
); CFReleaseSafe(error
); });
1066 certRef
= SecCertificateCreateWithData(kCFAllocatorDefault
, tokenCertData
);
1067 CFRelease(tokenCertData
);
1070 certRef
= SecCertificateCreateWithData(kCFAllocatorDefault
, certData
);
1076 bool match_item(SecDbConnectionRef dbt
, Query
*q
, CFArrayRef accessGroups
, CFDictionaryRef item
)
1079 SecCertificateRef certRef
= NULL
;
1080 if (q
->q_match_issuer
) {
1081 CFDataRef issuer
= CFDictionaryGetValue(item
, kSecAttrIssuer
);
1082 if (!items_matching_issuer_parent(dbt
, accessGroups
, q
->q_musrView
, issuer
, q
->q_match_issuer
, 10 /*max depth*/))
1086 if (q
->q_match_policy
&& (q
->q_class
== &identity_class
|| q
->q_class
== &cert_class
)) {
1088 certRef
= CopyCertificateFromItem(q
, item
);
1089 require_quiet(certRef
, out
);
1090 require_quiet(_FilterWithPolicy(q
->q_match_policy
, q
->q_match_valid_on_date
, certRef
), out
);
1093 if (q
->q_match_valid_on_date
&& (q
->q_class
== &identity_class
|| q
->q_class
== &cert_class
)) {
1095 certRef
= CopyCertificateFromItem(q
, item
);
1096 require_quiet(certRef
, out
);
1097 require_quiet(_FilterWithDate(q
->q_match_valid_on_date
, certRef
), out
);
1100 if (q
->q_match_trusted_only
&& (q
->q_class
== &identity_class
|| q
->q_class
== &cert_class
)) {
1102 certRef
= CopyCertificateFromItem(q
, item
);
1103 require_quiet(certRef
, out
);
1104 require_quiet(_FilterWithTrust(CFBooleanGetValue(q
->q_match_trusted_only
), certRef
), out
);
1107 /* Add future match checks here. */
1110 CFReleaseSafe(certRef
);
1114 /****************************************************************************
1115 **************** Beginning of Externally Callable Interface ****************
1116 ****************************************************************************/
1118 void (*SecTaskDiagnoseEntitlements
)(CFArrayRef accessGroups
) = NULL
;
1120 /* AUDIT[securityd](done):
1121 query (ok) is a caller provided dictionary, only its cf type has been checked.
1124 SecItemServerCopyMatching(CFDictionaryRef query
, CFTypeRef
*result
,
1125 SecurityClient
*client
, CFErrorRef
*error
)
1127 CFArrayRef accessGroups
= client
->accessGroups
;
1130 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
))) {
1131 if (SecTaskDiagnoseEntitlements
)
1132 SecTaskDiagnoseEntitlements(accessGroups
);
1133 return SecError(errSecMissingEntitlement
, error
,
1134 CFSTR("client has neither application-identifier nor keychain-access-groups entitlements"));
1137 if (CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), CFSTR("*"))) {
1138 /* Having the special accessGroup "*" allows access to all accessGroups. */
1139 accessGroups
= NULL
;
1143 Query
*q
= query_create_with_limit(query
, client
->musr
, 1, error
);
1145 CFStringRef agrp
= CFDictionaryGetValue(q
->q_item
, kSecAttrAccessGroup
);
1146 if (agrp
&& accessGroupsAllows(accessGroups
, agrp
)) {
1147 // TODO: Return an error if agrp is not NULL and accessGroupsAllows() fails above.
1148 const void *val
= agrp
;
1149 accessGroups
= CFArrayCreate(0, &val
, 1, &kCFTypeArrayCallBacks
);
1151 CFRetainSafe(accessGroups
);
1154 #if TARGET_OS_IPHONE
1155 if (q
->q_sync_bubble
&& client
->inMultiUser
) {
1156 CFReleaseNull(q
->q_musrView
);
1157 q
->q_musrView
= SecMUSRCreateSyncBubbleUserUUID(q
->q_sync_bubble
);
1158 } else if (client
->inMultiUser
&& client
->isNetworkExtension
) {
1159 CFReleaseNull(q
->q_musrView
);
1160 q
->q_musrView
= SecMUSRCreateBothUserAndSystemUUID(client
->uid
);
1161 } else if (q
->q_system_keychain
&& client
->inMultiUser
) {
1162 CFReleaseNull(q
->q_musrView
);
1163 q
->q_musrView
= SecMUSRCopySystemKeychainUUID();
1165 q
->q_system_keychain
= false;
1169 query_set_caller_access_groups(q
, accessGroups
);
1171 /* Sanity check the query. */
1172 if (q
->q_system_keychain
&& !client
->allowSystemKeychain
) {
1173 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for system keychain"));
1174 } else if (q
->q_sync_bubble
&& !client
->allowSyncBubbleKeychain
) {
1175 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for syncbubble keychain"));
1176 } else if (q
->q_system_keychain
&& q
->q_sync_bubble
) {
1177 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("can't do both system and syncbubble keychain"));
1178 } else if (q
->q_use_item_list
) {
1179 ok
= SecError(errSecUseItemListUnsupported
, error
, CFSTR("use item list unsupported"));
1180 } else if (q
->q_match_issuer
&& ((q
->q_class
!= &cert_class
) &&
1181 (q
->q_class
!= &identity_class
))) {
1182 ok
= SecError(errSecUnsupportedOperation
, error
, CFSTR("unsupported match attribute"));
1183 } else if (q
->q_match_policy
&& ((q
->q_class
!= &cert_class
) &&
1184 (q
->q_class
!= &identity_class
))) {
1185 ok
= SecError(errSecUnsupportedOperation
, error
, CFSTR("unsupported kSecMatchPolicy attribute"));
1186 } else if (q
->q_return_type
!= 0 && result
== NULL
) {
1187 ok
= SecError(errSecReturnMissingPointer
, error
, CFSTR("missing pointer"));
1188 } else if (!q
->q_error
) {
1189 ok
= kc_with_dbt(false, error
, ^(SecDbConnectionRef dbt
) {
1190 return s3dl_copy_matching(dbt
, q
, result
, accessGroups
, error
);
1194 CFReleaseSafe(accessGroups
);
1195 if (!query_destroy(q
, error
))
1203 _SecItemCopyMatching(CFDictionaryRef query
, SecurityClient
*client
, CFTypeRef
*result
, CFErrorRef
*error
) {
1204 return SecItemServerCopyMatching(query
, result
, client
, error
);
1207 #if TARGET_OS_IPHONE
1209 SecItemSynchronizable(CFDictionaryRef query
)
1211 bool result
= false;
1212 CFTypeRef value
= CFDictionaryGetValue(query
, kSecAttrSynchronizable
);
1213 if (isBoolean(value
))
1214 return CFBooleanGetValue(value
);
1215 else if (isNumber(value
)) {
1217 (void)CFNumberGetValue(value
, kCFNumberSInt32Type
, &number
);
1226 /* AUDIT[securityd](done):
1227 attributes (ok) is a caller provided dictionary, only its cf type has
1231 _SecItemAdd(CFDictionaryRef attributes
, SecurityClient
*client
, CFTypeRef
*result
, CFErrorRef
*error
)
1233 CFArrayRef accessGroups
= client
->accessGroups
;
1237 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
)) ||
1238 (ag_count
== 1 && CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), kSecAttrAccessGroupToken
))) {
1239 if (SecTaskDiagnoseEntitlements
)
1240 SecTaskDiagnoseEntitlements(accessGroups
);
1241 return SecError(errSecMissingEntitlement
, error
,
1242 CFSTR("client has neither application-identifier nor keychain-access-groups entitlements"));
1245 Query
*q
= query_create_with_limit(attributes
, client
->musr
, 0, error
);
1247 /* Access group sanity checking. */
1248 CFStringRef agrp
= (CFStringRef
)CFDictionaryGetValue(attributes
,
1249 kSecAttrAccessGroup
);
1251 /* Having the special accessGroup "*" allows access to all accessGroups. */
1252 if (CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), CFSTR("*")))
1253 accessGroups
= NULL
;
1256 /* The user specified an explicit access group, validate it. */
1257 if (!accessGroupsAllows(accessGroups
, agrp
))
1258 ok
= SecError(errSecMissingEntitlement
, error
,
1259 CFSTR("explicit accessGroup %@ not in client access %@"), agrp
, accessGroups
);
1261 agrp
= (CFStringRef
)CFArrayGetValueAtIndex(client
->accessGroups
, 0);
1263 /* We are using an implicit access group, add it as if the user
1264 specified it as an attribute. */
1265 query_add_attribute(kSecAttrAccessGroup
, agrp
, q
);
1267 #if TARGET_OS_IPHONE
1268 if (q
->q_system_keychain
&& client
->inMultiUser
) {
1269 CFReleaseNull(q
->q_musrView
);
1270 q
->q_musrView
= SecMUSRCopySystemKeychainUUID();
1272 q
->q_system_keychain
= false;
1274 query_add_attribute_with_desc(&v8musr
, q
->q_musrView
, q
);
1278 query_ensure_access_control(q
, agrp
);
1280 if (q
->q_system_keychain
&& !client
->allowSystemKeychain
) {
1281 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for system keychain"));
1282 } else if (q
->q_sync_bubble
&& !client
->allowSyncBubbleKeychain
) {
1283 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for syncbubble keychain"));
1284 #if TARGET_OS_IPHONE
1285 } else if (q
->q_system_keychain
&& SecItemSynchronizable(attributes
) && !client
->inMultiUser
) {
1286 ok
= SecError(errSecInvalidKey
, error
, CFSTR("Can't store system keychain and synchronizable"));
1288 } else if (q
->q_row_id
) {
1289 ok
= SecError(errSecValuePersistentRefUnsupported
, error
, CFSTR("q_row_id")); // TODO: better error string
1290 } else if (!q
->q_error
) {
1291 ok
= kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
){
1292 return kc_transaction(dbt
, error
, ^{
1293 query_pre_add(q
, true);
1294 return s3dl_query_add(dbt
, q
, result
, error
);
1299 ok
= query_notify_and_destroy(q
, ok
, error
);
1306 /* AUDIT[securityd](done):
1307 query (ok) and attributesToUpdate (ok) are a caller provided dictionaries,
1308 only their cf types have been checked.
1311 _SecItemUpdate(CFDictionaryRef query
, CFDictionaryRef attributesToUpdate
,
1312 SecurityClient
*client
, CFErrorRef
*error
)
1314 CFArrayRef accessGroups
= client
->accessGroups
;
1317 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
)) ||
1318 (ag_count
== 1 && CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), kSecAttrAccessGroupToken
))) {
1319 if (SecTaskDiagnoseEntitlements
)
1320 SecTaskDiagnoseEntitlements(accessGroups
);
1321 return SecError(errSecMissingEntitlement
, error
,
1322 CFSTR("client has neither application-identifier nor keychain-access-groups entitlements"));
1325 if (CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), CFSTR("*"))) {
1326 /* Having the special accessGroup "*" allows access to all accessGroups. */
1327 accessGroups
= NULL
;
1331 Query
*q
= query_create_with_limit(query
, client
->musr
, kSecMatchUnlimited
, error
);
1336 #if TARGET_OS_IPHONE
1337 if (q
->q_system_keychain
&& client
->inMultiUser
) {
1338 CFReleaseNull(q
->q_musrView
);
1339 q
->q_musrView
= SecMUSRCopySystemKeychainUUID();
1341 q
->q_system_keychain
= false;
1345 /* Sanity check the query. */
1346 query_set_caller_access_groups(q
, accessGroups
);
1347 if (q
->q_system_keychain
&& !client
->allowSystemKeychain
) {
1348 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for system keychain"));
1349 } else if (q
->q_sync_bubble
&& !client
->allowSyncBubbleKeychain
) {
1350 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for syncbubble keychain"));
1351 #if TARGET_OS_IPHONE
1352 } else if (q
->q_system_keychain
&& SecItemSynchronizable(attributesToUpdate
) && !client
->inMultiUser
) {
1353 ok
= SecError(errSecInvalidKey
, error
, CFSTR("Can't update an system keychain item with synchronizable"));
1355 } else if (q
->q_use_item_list
) {
1356 ok
= SecError(errSecUseItemListUnsupported
, error
, CFSTR("use item list not supported"));
1357 } else if (q
->q_return_type
& kSecReturnDataMask
) {
1358 /* Update doesn't return anything so don't ask for it. */
1359 ok
= SecError(errSecReturnDataUnsupported
, error
, CFSTR("return data not supported by update"));
1360 } else if (q
->q_return_type
& kSecReturnAttributesMask
) {
1361 ok
= SecError(errSecReturnAttributesUnsupported
, error
, CFSTR("return attributes not supported by update"));
1362 } else if (q
->q_return_type
& kSecReturnRefMask
) {
1363 ok
= SecError(errSecReturnRefUnsupported
, error
, CFSTR("return ref not supported by update"));
1364 } else if (q
->q_return_type
& kSecReturnPersistentRefMask
) {
1365 ok
= SecError(errSecReturnPersistentRefUnsupported
, error
, CFSTR("return persistent ref not supported by update"));
1367 /* Access group sanity checking. */
1368 CFStringRef agrp
= (CFStringRef
)CFDictionaryGetValue(attributesToUpdate
,
1369 kSecAttrAccessGroup
);
1371 /* The user is attempting to modify the access group column,
1372 validate it to make sure the new value is allowable. */
1373 if (!accessGroupsAllows(accessGroups
, agrp
)) {
1374 ok
= SecError(errSecNoAccessForItem
, error
, CFSTR("accessGroup %@ not in %@"), agrp
, accessGroups
);
1380 ok
= kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
1381 return kc_transaction(dbt
, error
, ^{
1382 return s3dl_query_update(dbt
, q
, attributesToUpdate
, accessGroups
, error
);
1387 ok
= query_notify_and_destroy(q
, ok
, error
);
1393 /* AUDIT[securityd](done):
1394 query (ok) is a caller provided dictionary, only its cf type has been checked.
1397 _SecItemDelete(CFDictionaryRef query
, SecurityClient
*client
, CFErrorRef
*error
)
1399 CFArrayRef accessGroups
= client
->accessGroups
;
1402 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
)) ||
1403 (ag_count
== 1 && CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), kSecAttrAccessGroupToken
))) {
1404 if (SecTaskDiagnoseEntitlements
)
1405 SecTaskDiagnoseEntitlements(accessGroups
);
1406 return SecError(errSecMissingEntitlement
, error
,
1407 CFSTR("client has neither application-identifier nor keychain-access-groups entitlements"));
1410 if (CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), CFSTR("*"))) {
1411 /* Having the special accessGroup "*" allows access to all accessGroups. */
1412 accessGroups
= NULL
;
1415 Query
*q
= query_create_with_limit(query
, client
->musr
, kSecMatchUnlimited
, error
);
1418 #if TARGET_OS_IPHONE
1419 if (q
->q_system_keychain
&& client
->inMultiUser
) {
1420 CFReleaseNull(q
->q_musrView
);
1421 q
->q_musrView
= SecMUSRCopySystemKeychainUUID();
1423 q
->q_system_keychain
= false;
1427 query_set_caller_access_groups(q
, accessGroups
);
1428 /* Sanity check the query. */
1429 if (q
->q_system_keychain
&& !client
->allowSystemKeychain
) {
1430 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for system keychain"));
1431 } else if (q
->q_sync_bubble
&& !client
->allowSyncBubbleKeychain
) {
1432 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for syncbubble keychain"));
1433 } else if (q
->q_limit
!= kSecMatchUnlimited
) {
1434 ok
= SecError(errSecMatchLimitUnsupported
, error
, CFSTR("match limit not supported by delete"));
1435 } else if (query_match_count(q
) != 0) {
1436 ok
= SecError(errSecItemMatchUnsupported
, error
, CFSTR("match not supported by delete"));
1437 } else if (q
->q_ref
) {
1438 ok
= SecError(errSecValueRefUnsupported
, error
, CFSTR("value ref not supported by delete"));
1439 } else if (q
->q_row_id
&& query_attr_count(q
)) {
1440 ok
= SecError(errSecItemIllegalQuery
, error
, CFSTR("rowid and other attributes are mutually exclusive"));
1442 ok
= kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
1443 return kc_transaction(dbt
, error
, ^{
1444 return s3dl_query_delete(dbt
, q
, accessGroups
, error
);
1448 ok
= query_notify_and_destroy(q
, ok
, error
);
1455 static bool SecItemDeleteTokenItems(SecDbConnectionRef dbt
, CFTypeRef classToDelete
, CFTypeRef tokenID
, CFArrayRef accessGroups
, SecurityClient
*client
, CFErrorRef
*error
) {
1456 CFTypeRef keys
[] = { kSecClass
, kSecAttrTokenID
};
1457 CFTypeRef values
[] = { classToDelete
, tokenID
};
1459 CFDictionaryRef query
= CFDictionaryCreate(kCFAllocatorDefault
, keys
, values
, 2, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1460 Query
*q
= query_create_with_limit(query
, client
->musr
, kSecMatchUnlimited
, error
);
1464 query_set_caller_access_groups(q
, accessGroups
);
1465 ok
= s3dl_query_delete(dbt
, q
, accessGroups
, error
);
1466 ok
= query_notify_and_destroy(q
, ok
, error
);
1474 static bool SecItemAddTokenItem(SecDbConnectionRef dbt
, CFDictionaryRef attributes
, CFArrayRef accessGroups
, SecurityClient
*client
, CFErrorRef
*error
) {
1476 Query
*q
= query_create_with_limit(attributes
, client
->musr
, 0, error
);
1478 CFStringRef agrp
= kSecAttrAccessGroupToken
;
1479 query_add_attribute(kSecAttrAccessGroup
, agrp
, q
);
1482 query_ensure_access_control(q
, agrp
);
1483 if (q
->q_system_keychain
&& !client
->allowSystemKeychain
) {
1484 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for system keychain"));
1485 } else if (q
->q_sync_bubble
&& !client
->allowSyncBubbleKeychain
) {
1486 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for syncbubble keychain"));
1487 } else if (q
->q_row_id
) {
1488 ok
= SecError(errSecValuePersistentRefUnsupported
, error
, CFSTR("q_row_id")); // TODO: better error string
1489 } else if (!q
->q_error
) {
1490 query_pre_add(q
, true);
1491 ok
= s3dl_query_add(dbt
, q
, NULL
, error
);
1494 ok
= query_notify_and_destroy(q
, ok
, error
);
1501 bool _SecItemUpdateTokenItems(CFStringRef tokenID
, CFArrayRef items
, SecurityClient
*client
, CFErrorRef
*error
) {
1503 CFArrayRef accessGroups
= client
->accessGroups
;
1505 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
))) {
1506 if (SecTaskDiagnoseEntitlements
)
1507 SecTaskDiagnoseEntitlements(accessGroups
);
1508 return SecError(errSecMissingEntitlement
, error
,
1509 CFSTR("client has neither application-identifier nor keychain-access-groups entitlements"));
1512 ok
= kc_with_dbt(true, error
, ^bool (SecDbConnectionRef dbt
) {
1513 return kc_transaction(dbt
, error
, ^bool {
1515 const CFTypeRef classToDelete
[] = { kSecClassGenericPassword
, kSecClassInternetPassword
, kSecClassCertificate
, kSecClassKey
};
1516 for (size_t i
= 0; i
< sizeof(classToDelete
) / sizeof(classToDelete
[0]); ++i
) {
1517 SecItemDeleteTokenItems(dbt
, classToDelete
[i
], tokenID
, accessGroups
, client
, NULL
);
1520 for (CFIndex i
= 0; i
< CFArrayGetCount(items
); ++i
) {
1521 if (!SecItemAddTokenItem(dbt
, CFArrayGetValueAtIndex(items
, i
), accessGroups
, client
, error
))
1527 const CFTypeRef classToDelete
[] = { kSecClassGenericPassword
, kSecClassInternetPassword
, kSecClassCertificate
, kSecClassKey
};
1528 bool deleted
= true;
1529 for (size_t i
= 0; i
< sizeof(classToDelete
) / sizeof(classToDelete
[0]); ++i
) {
1530 if (!SecItemDeleteTokenItems(dbt
, classToDelete
[i
], tokenID
, accessGroups
, client
, error
) && error
&& CFErrorGetCode(*error
) != errSecItemNotFound
) {
1534 else if (error
&& *error
) {
1535 CFReleaseNull(*error
);
1546 /* AUDIT[securityd](done):
1547 No caller provided inputs.
1550 SecItemServerDeleteAll(CFErrorRef
*error
) {
1551 return kc_with_dbt(true, error
, ^bool (SecDbConnectionRef dbt
) {
1552 return (kc_transaction(dbt
, error
, ^bool {
1553 return (SecDbExec(dbt
, CFSTR("DELETE from genp;"), error
) &&
1554 SecDbExec(dbt
, CFSTR("DELETE from inet;"), error
) &&
1555 SecDbExec(dbt
, CFSTR("DELETE from cert;"), error
) &&
1556 SecDbExec(dbt
, CFSTR("DELETE from keys;"), error
));
1557 }) && SecDbExec(dbt
, CFSTR("VACUUM;"), error
));
1562 _SecItemDeleteAll(CFErrorRef
*error
) {
1563 return SecItemServerDeleteAll(error
);
1567 _SecItemServerDeleteAllWithAccessGroups(CFArrayRef accessGroups
, SecurityClient
*client
, CFErrorRef
*error
)
1569 __block
bool ok
= true;
1570 static dispatch_once_t onceToken
;
1571 static CFSetRef illegalAccessGroups
= NULL
;
1573 dispatch_once(&onceToken
, ^{
1574 const CFStringRef values
[] = {
1577 CFSTR("com.apple.security.sos"),
1578 CFSTR("lockdown-identities"),
1580 illegalAccessGroups
= CFSetCreate(NULL
, (const void **)values
, sizeof(values
)/sizeof(values
[0]), &kCFTypeSetCallBacks
);
1583 static const CFTypeRef qclasses
[] = {
1590 require_action_quiet(isArray(accessGroups
), fail
,
1592 SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType
, sSecXPCErrorDomain
, NULL
, error
, NULL
, CFSTR("accessGroups not CFArray, got %@"), accessGroups
));
1594 // TODO: whitelist instead? look for dev IDs like 7123498YQX.com.somedev.app
1596 require_action(CFArrayGetCount(accessGroups
) != 0, fail
,
1598 SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType
, sSecXPCErrorDomain
, NULL
, error
, NULL
, CFSTR("accessGroups e empty")));
1601 // Pre-check accessGroups for prohibited values
1602 CFArrayForEach(accessGroups
, ^(const void *value
) {
1603 CFStringRef agrp
= (CFStringRef
)value
;
1605 if (!isString(agrp
)) {
1606 SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType
, sSecXPCErrorDomain
, NULL
, error
, NULL
,
1607 CFSTR("access not a string: %@"), agrp
);
1609 } else if (CFSetContainsValue(illegalAccessGroups
, agrp
)) {
1610 SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType
, sSecXPCErrorDomain
, NULL
, error
, NULL
,
1611 CFSTR("illegal access group: %@"), accessGroups
);
1617 ok
= kc_with_dbt(true, error
, ^bool(SecDbConnectionRef dbt
) {
1618 return kc_transaction(dbt
, error
, ^bool {
1619 CFErrorRef localError
= NULL
;
1623 for (n
= 0; n
< sizeof(qclasses
)/sizeof(qclasses
[0]) && ok1
; n
++) {
1626 q
= query_create(qclasses
[n
], client
->musr
, NULL
, error
);
1629 (void)s3dl_query_delete(dbt
, q
, accessGroups
, &localError
);
1631 query_destroy(q
, error
);
1632 CFReleaseNull(localError
);
1635 }) && SecDbExec(dbt
, CFSTR("VACUUM"), error
);
1644 // MARK: Shared web credentials
1647 #define SEC_CONST_DECL(k,v) const CFStringRef k = CFSTR(v);
1649 SEC_CONST_DECL (kSecSafariAccessGroup
, "com.apple.cfnetwork");
1650 SEC_CONST_DECL (kSecSafariDefaultComment
, "default");
1651 SEC_CONST_DECL (kSecSafariPasswordsNotSaved
, "Passwords not saved");
1652 SEC_CONST_DECL (kSecSharedCredentialUrlScheme
, "https://");
1653 SEC_CONST_DECL (kSecSharedWebCredentialsService
, "webcredentials");
1655 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1656 static dispatch_once_t sSecSWCInitializeOnce
= 0;
1657 static void * sSecSWCLibrary
= NULL
;
1658 static SWCCheckService_f sSWCCheckService_f
= NULL
;
1659 static SWCSetServiceFlags_f sSWCSetServiceFlags_f
= NULL
;
1661 static OSStatus
_SecSWCEnsuredInitialized(void);
1663 static OSStatus
_SecSWCEnsuredInitialized(void)
1665 __block OSStatus status
= errSecNotAvailable
;
1667 dispatch_once(&sSecSWCInitializeOnce
, ^{
1668 sSecSWCLibrary
= dlopen("/System/Library/PrivateFrameworks/SharedWebCredentials.framework/SharedWebCredentials", RTLD_LAZY
| RTLD_LOCAL
);
1669 assert(sSecSWCLibrary
);
1670 if (sSecSWCLibrary
) {
1671 sSWCCheckService_f
= (SWCCheckService_f
)(uintptr_t) dlsym(sSecSWCLibrary
, "SWCCheckService");
1672 sSWCSetServiceFlags_f
= (SWCSetServiceFlags_f
)(uintptr_t) dlsym(sSecSWCLibrary
, "SWCSetServiceFlags");
1676 if (sSWCCheckService_f
&& sSWCSetServiceFlags_f
) {
1683 #if !TARGET_IPHONE_SIMULATOR
1685 _SecAppDomainApprovalStatus(CFStringRef appID
, CFStringRef fqdn
, CFErrorRef
*error
)
1687 __block SWCFlags flags
= kSWCFlags_None
;
1689 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1690 OSStatus status
= _SecSWCEnsuredInitialized();
1692 SecError(status
, error
, CFSTR("SWC initialize failed"));
1695 CFRetainSafe(appID
);
1697 dispatch_semaphore_t semaphore
= dispatch_semaphore_create(0);
1698 dispatch_retain(semaphore
);
1699 if (0 == sSWCCheckService_f(kSecSharedWebCredentialsService
, appID
, fqdn
,
1700 ^void (OSStatus inStatus
, SWCFlags inFlags
, CFDictionaryRef inDetails
) {
1701 if (!inStatus
) { flags
= inFlags
; }
1702 CFReleaseSafe(appID
);
1703 CFReleaseSafe(fqdn
);
1704 dispatch_semaphore_signal(semaphore
);
1705 dispatch_release(semaphore
);
1706 //secerror("SWCCheckService: inStatus=%d, flags=%0X", inStatus, flags);
1709 // wait for the block to complete, as we need its answer
1710 dispatch_semaphore_wait(semaphore
, DISPATCH_TIME_FOREVER
);
1712 else // didn't queue the block
1714 CFReleaseSafe(appID
);
1715 CFReleaseSafe(fqdn
);
1716 dispatch_release(semaphore
);
1718 dispatch_release(semaphore
);
1720 flags
|= (kSWCFlag_SiteApproved
);
1723 if (!error
) { return flags
; }
1726 // check website approval status
1727 if (!(flags
& kSWCFlag_SiteApproved
)) {
1728 if (flags
& kSWCFlag_Pending
) {
1729 SecError(errSecAuthFailed
, error
, CFSTR("Approval is pending for \"%@\", try later"), fqdn
);
1731 SecError(errSecAuthFailed
, error
, CFSTR("\"%@\" failed to approve \"%@\""), fqdn
, appID
);
1736 // check user approval status
1737 if (flags
& kSWCFlag_UserDenied
) {
1738 SecError(errSecAuthFailed
, error
, CFSTR("User denied access to \"%@\" by \"%@\""), fqdn
, appID
);
1744 #if !TARGET_IPHONE_SIMULATOR
1746 _SecEntitlementContainsDomainForService(CFArrayRef domains
, CFStringRef domain
, CFStringRef service
)
1748 bool result
= false;
1749 CFIndex idx
, count
= (domains
) ? CFArrayGetCount(domains
) : (CFIndex
) 0;
1750 if (!count
|| !domain
|| !service
) {
1753 for (idx
=0; idx
< count
; idx
++) {
1754 CFStringRef str
= (CFStringRef
) CFArrayGetValueAtIndex(domains
, idx
);
1755 if (str
&& CFStringHasPrefix(str
, kSecSharedWebCredentialsService
)) {
1756 CFIndex prefix_len
= CFStringGetLength(kSecSharedWebCredentialsService
)+1;
1757 CFIndex substr_len
= CFStringGetLength(str
) - prefix_len
;
1758 CFRange range
= { prefix_len
, substr_len
};
1759 CFStringRef substr
= CFStringCreateWithSubstring(kCFAllocatorDefault
, str
, range
);
1760 if (substr
&& CFEqual(substr
, domain
)) {
1763 CFReleaseSafe(substr
);
1774 _SecAddNegativeWebCredential(SecurityClient
*client
, CFStringRef fqdn
, CFStringRef appID
, bool forSafari
)
1776 bool result
= false;
1777 if (!fqdn
) { return result
; }
1779 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1780 OSStatus status
= _SecSWCEnsuredInitialized();
1781 if (status
) { return false; }
1783 // update our database
1784 CFRetainSafe(appID
);
1786 if (0 == sSWCSetServiceFlags_f(kSecSharedWebCredentialsService
,
1787 appID
, fqdn
, kSWCFlag_ExternalMask
, kSWCFlag_UserDenied
,
1788 ^void(OSStatus inStatus
, SWCFlags inNewFlags
){
1789 CFReleaseSafe(appID
);
1790 CFReleaseSafe(fqdn
);
1795 else // didn't queue the block
1797 CFReleaseSafe(appID
);
1798 CFReleaseSafe(fqdn
);
1801 if (!forSafari
) { return result
; }
1803 // below this point: create a negative Safari web credential item
1805 CFMutableDictionaryRef attrs
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1806 if (!attrs
) { return result
; }
1808 CFErrorRef error
= NULL
;
1809 CFStringRef accessGroup
= CFSTR("*");
1810 SecurityClient swcclient
= {
1812 .accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&accessGroup
, 1, &kCFTypeArrayCallBacks
),
1813 .allowSystemKeychain
= false,
1814 .allowSyncBubbleKeychain
= false,
1815 .isNetworkExtension
= false,
1816 .musr
= client
->musr
,
1819 CFDictionaryAddValue(attrs
, kSecClass
, kSecClassInternetPassword
);
1820 CFDictionaryAddValue(attrs
, kSecAttrAccessGroup
, kSecSafariAccessGroup
);
1821 CFDictionaryAddValue(attrs
, kSecAttrAuthenticationType
, kSecAttrAuthenticationTypeHTMLForm
);
1822 CFDictionaryAddValue(attrs
, kSecAttrProtocol
, kSecAttrProtocolHTTPS
);
1823 CFDictionaryAddValue(attrs
, kSecAttrServer
, fqdn
);
1824 CFDictionaryAddValue(attrs
, kSecAttrSynchronizable
, kCFBooleanTrue
);
1826 (void)_SecItemDelete(attrs
, &swcclient
, &error
);
1827 CFReleaseNull(error
);
1829 CFDictionaryAddValue(attrs
, kSecAttrAccount
, kSecSafariPasswordsNotSaved
);
1830 CFDictionaryAddValue(attrs
, kSecAttrComment
, kSecSafariDefaultComment
);
1832 CFStringRef label
= CFStringCreateWithFormat(kCFAllocatorDefault
,
1833 NULL
, CFSTR("%@ (%@)"), fqdn
, kSecSafariPasswordsNotSaved
);
1835 CFDictionaryAddValue(attrs
, kSecAttrLabel
, label
);
1836 CFReleaseSafe(label
);
1840 CFDataRef data
= CFDataCreate(kCFAllocatorDefault
, &space
, 1);
1842 CFDictionarySetValue(attrs
, kSecValueData
, data
);
1843 CFReleaseSafe(data
);
1846 CFTypeRef addResult
= NULL
;
1847 result
= _SecItemAdd(attrs
, &swcclient
, &addResult
, &error
);
1849 CFReleaseSafe(addResult
);
1850 CFReleaseSafe(error
);
1851 CFReleaseSafe(attrs
);
1852 CFReleaseSafe(swcclient
.accessGroups
);
1857 /* Specialized version of SecItemAdd for shared web credentials */
1859 _SecAddSharedWebCredential(CFDictionaryRef attributes
,
1860 SecurityClient
*client
,
1861 const audit_token_t
*clientAuditToken
,
1868 SecurityClient swcclient
= {};
1870 CFStringRef fqdn
= CFRetainSafe(CFDictionaryGetValue(attributes
, kSecAttrServer
));
1871 CFStringRef account
= CFDictionaryGetValue(attributes
, kSecAttrAccount
);
1872 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH && !TARGET_OS_TV
1873 CFStringRef password
= CFDictionaryGetValue(attributes
, kSecSharedPassword
);
1875 CFStringRef password
= CFDictionaryGetValue(attributes
, CFSTR("spwd"));
1877 CFStringRef accessGroup
= CFSTR("*");
1878 CFMutableDictionaryRef query
= NULL
, attrs
= NULL
;
1882 // check autofill enabled status
1883 if (!swca_autofill_enabled(clientAuditToken
)) {
1884 SecError(errSecBadReq
, error
, CFSTR("Autofill is not enabled in Safari settings"));
1888 // parse fqdn with CFURL here, since it could be specified as domain:port
1890 CFStringRef urlStr
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%@%@"), kSecSharedCredentialUrlScheme
, fqdn
);
1892 CFURLRef url
= CFURLCreateWithString(kCFAllocatorDefault
, urlStr
, nil
);
1894 CFStringRef hostname
= CFURLCopyHostName(url
);
1896 CFReleaseSafe(fqdn
);
1898 port
= CFURLGetPortNumber(url
);
1902 CFReleaseSafe(urlStr
);
1907 SecError(errSecParam
, error
, CFSTR("No account provided"));
1911 SecError(errSecParam
, error
, CFSTR("No domain provided"));
1915 #if TARGET_IPHONE_SIMULATOR
1916 secerror("app/site association entitlements not checked in Simulator");
1918 OSStatus status
= errSecMissingEntitlement
;
1919 // validate that fqdn is part of caller's shared credential domains entitlement
1921 SecError(status
, error
, CFSTR("Missing application-identifier entitlement"));
1924 if (_SecEntitlementContainsDomainForService(domains
, fqdn
, kSecSharedWebCredentialsService
)) {
1925 status
= errSecSuccess
;
1927 if (errSecSuccess
!= status
) {
1928 CFStringRef msg
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
,
1929 CFSTR("%@ not found in %@ entitlement"), fqdn
, kSecEntitlementAssociatedDomains
);
1931 msg
= CFRetain(CFSTR("Requested domain not found in entitlement"));
1933 SecError(status
, error
, CFSTR("%@"), msg
);
1939 #if TARGET_IPHONE_SIMULATOR
1940 secerror("Ignoring app/site approval state in the Simulator.");
1942 // get approval status for this app/domain pair
1943 SWCFlags flags
= _SecAppDomainApprovalStatus(appID
, fqdn
, error
);
1944 if (!(flags
& kSWCFlag_SiteApproved
)) {
1949 // give ourselves access to see matching items for kSecSafariAccessGroup
1950 swcclient
.task
= NULL
;
1951 swcclient
.accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&accessGroup
, 1, &kCFTypeArrayCallBacks
);
1952 swcclient
.allowSystemKeychain
= false;
1953 swcclient
.musr
= client
->musr
;
1954 swcclient
.allowSystemKeychain
= false;
1955 swcclient
.allowSyncBubbleKeychain
= false;
1956 swcclient
.isNetworkExtension
= false;
1959 // create lookup query
1960 query
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1962 SecError(errSecAllocate
, error
, CFSTR("Unable to create query dictionary"));
1965 CFDictionaryAddValue(query
, kSecClass
, kSecClassInternetPassword
);
1966 CFDictionaryAddValue(query
, kSecAttrAccessGroup
, kSecSafariAccessGroup
);
1967 CFDictionaryAddValue(query
, kSecAttrAuthenticationType
, kSecAttrAuthenticationTypeHTMLForm
);
1968 CFDictionaryAddValue(query
, kSecAttrServer
, fqdn
);
1969 CFDictionaryAddValue(query
, kSecAttrSynchronizable
, kCFBooleanTrue
);
1971 // check for presence of Safari's negative entry ('passwords not saved')
1972 CFDictionarySetValue(query
, kSecAttrAccount
, kSecSafariPasswordsNotSaved
);
1973 ok
= _SecItemCopyMatching(query
, &swcclient
, result
, error
);
1974 if(result
) CFReleaseNull(*result
);
1975 if (error
) CFReleaseNull(*error
);
1977 SecError(errSecDuplicateItem
, error
, CFSTR("Item already exists for this server"));
1981 // now use the provided account (and optional port number, if one was present)
1982 CFDictionarySetValue(query
, kSecAttrAccount
, account
);
1983 if (port
< -1 || port
> 0) {
1984 SInt16 portValueShort
= (port
& 0xFFFF);
1985 CFNumberRef portNumber
= CFNumberCreate(NULL
, kCFNumberSInt16Type
, &portValueShort
);
1986 CFDictionaryAddValue(query
, kSecAttrPort
, portNumber
);
1987 CFReleaseSafe(portNumber
);
1990 // look up existing password
1991 if (_SecItemCopyMatching(query
, &swcclient
, result
, error
)) {
1992 // found it, so this becomes either an "update password" or "delete password" operation
1993 if(result
) CFReleaseNull(*result
);
1994 if(error
) CFReleaseNull(*error
);
1995 bool update
= (password
!= NULL
);
1997 attrs
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1998 CFDataRef credential
= CFStringCreateExternalRepresentation(kCFAllocatorDefault
, password
, kCFStringEncodingUTF8
, 0);
1999 CFDictionaryAddValue(attrs
, kSecValueData
, credential
);
2000 CFReleaseSafe(credential
);
2001 CFDictionaryAddValue(attrs
, kSecAttrComment
, kSecSafariDefaultComment
);
2003 // confirm the update
2004 // (per rdar://16676310 we always prompt, even if there was prior user approval)
2005 ok
= /*approved ||*/ swca_confirm_operation(swca_update_request_id
, clientAuditToken
, query
, error
,
2006 ^void (CFStringRef fqdn
) { _SecAddNegativeWebCredential(client
, fqdn
, appID
, false); });
2008 ok
= _SecItemUpdate(query
, attrs
, &swcclient
, error
);
2012 // confirm the delete
2013 // (per rdar://16676288 we always prompt, even if there was prior user approval)
2014 ok
= /*approved ||*/ swca_confirm_operation(swca_delete_request_id
, clientAuditToken
, query
, error
,
2015 ^void (CFStringRef fqdn
) { _SecAddNegativeWebCredential(client
, fqdn
, appID
, false); });
2017 ok
= _SecItemDelete(query
, &swcclient
, error
);
2021 if (error
) CFReleaseNull(*error
);
2025 if (result
) CFReleaseNull(*result
);
2026 if (error
) CFReleaseNull(*error
);
2028 // password does not exist, so prepare to add it
2030 // a NULL password value removes the existing credential. Since we didn't find it, this is a no-op.
2035 CFStringRef label
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%@ (%@)"), fqdn
, account
);
2037 CFDictionaryAddValue(query
, kSecAttrLabel
, label
);
2038 CFReleaseSafe(label
);
2040 // NOTE: we always expect to use HTTPS for web forms.
2041 CFDictionaryAddValue(query
, kSecAttrProtocol
, kSecAttrProtocolHTTPS
);
2043 CFDataRef credential
= CFStringCreateExternalRepresentation(kCFAllocatorDefault
, password
, kCFStringEncodingUTF8
, 0);
2044 CFDictionarySetValue(query
, kSecValueData
, credential
);
2045 CFReleaseSafe(credential
);
2046 CFDictionarySetValue(query
, kSecAttrComment
, kSecSafariDefaultComment
);
2048 CFReleaseSafe(swcclient
.accessGroups
);
2049 swcclient
.accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&kSecSafariAccessGroup
, 1, &kCFTypeArrayCallBacks
);
2051 // mark the item as created by this function
2052 const int32_t creator_value
= 'swca';
2053 CFNumberRef creator
= CFNumberCreate(kCFAllocatorDefault
, kCFNumberSInt32Type
, &creator_value
);
2055 CFDictionarySetValue(query
, kSecAttrCreator
, creator
);
2056 CFReleaseSafe(creator
);
2061 // (per rdar://16680019, we won't prompt here in the normal case)
2062 ok
= /*approved ||*/ swca_confirm_operation(swca_add_request_id
, clientAuditToken
, query
, error
,
2063 ^void (CFStringRef fqdn
) { _SecAddNegativeWebCredential(client
, fqdn
, appID
, false); });
2067 ok
= _SecItemAdd(query
, &swcclient
, result
, error
);
2071 CFReleaseSafe(attrs
);
2072 CFReleaseSafe(query
);
2073 CFReleaseSafe(swcclient
.accessGroups
);
2074 CFReleaseSafe(fqdn
);
2078 /* Specialized version of SecItemCopyMatching for shared web credentials */
2080 _SecCopySharedWebCredential(CFDictionaryRef query
,
2081 SecurityClient
*client
,
2082 const audit_token_t
*clientAuditToken
,
2088 CFMutableArrayRef credentials
= NULL
;
2089 CFMutableArrayRef foundItems
= NULL
;
2090 CFMutableArrayRef fqdns
= NULL
;
2091 CFStringRef fqdn
= NULL
;
2092 CFStringRef account
= NULL
;
2097 require_quiet(result
, cleanup
);
2098 credentials
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
2099 foundItems
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
2100 fqdns
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
2102 // give ourselves access to see matching items for kSecSafariAccessGroup
2103 CFStringRef accessGroup
= CFSTR("*");
2104 SecurityClient swcclient
= {
2106 .accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&accessGroup
, 1, &kCFTypeArrayCallBacks
),
2107 .allowSystemKeychain
= false,
2108 .allowSyncBubbleKeychain
= false,
2109 .isNetworkExtension
= false,
2110 .musr
= client
->musr
,
2113 // On input, the query dictionary contains optional fqdn and account entries.
2114 fqdn
= CFDictionaryGetValue(query
, kSecAttrServer
);
2115 account
= CFDictionaryGetValue(query
, kSecAttrAccount
);
2117 // Check autofill enabled status
2118 if (!swca_autofill_enabled(clientAuditToken
)) {
2119 SecError(errSecBadReq
, error
, CFSTR("Autofill is not enabled in Safari settings"));
2123 // Check fqdn; if NULL, add domains from caller's entitlement.
2125 CFArrayAppendValue(fqdns
, fqdn
);
2128 CFIndex idx
, count
= CFArrayGetCount(domains
);
2129 for (idx
=0; idx
< count
; idx
++) {
2130 CFStringRef str
= (CFStringRef
) CFArrayGetValueAtIndex(domains
, idx
);
2131 // Parse the entry for our service label prefix
2132 if (str
&& CFStringHasPrefix(str
, kSecSharedWebCredentialsService
)) {
2133 CFIndex prefix_len
= CFStringGetLength(kSecSharedWebCredentialsService
)+1;
2134 CFIndex substr_len
= CFStringGetLength(str
) - prefix_len
;
2135 CFRange range
= { prefix_len
, substr_len
};
2136 fqdn
= CFStringCreateWithSubstring(kCFAllocatorDefault
, str
, range
);
2138 CFArrayAppendValue(fqdns
, fqdn
);
2144 count
= CFArrayGetCount(fqdns
);
2146 SecError(errSecParam
, error
, CFSTR("No domain provided"));
2150 // Aggregate search results for each domain
2151 for (idx
= 0; idx
< count
; idx
++) {
2152 CFMutableArrayRef items
= NULL
;
2153 CFMutableDictionaryRef attrs
= NULL
;
2154 fqdn
= (CFStringRef
) CFArrayGetValueAtIndex(fqdns
, idx
);
2158 // Parse the fqdn for a possible port specifier.
2160 CFStringRef urlStr
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%@%@"), kSecSharedCredentialUrlScheme
, fqdn
);
2162 CFURLRef url
= CFURLCreateWithString(kCFAllocatorDefault
, urlStr
, nil
);
2164 CFStringRef hostname
= CFURLCopyHostName(url
);
2166 CFReleaseSafe(fqdn
);
2168 port
= CFURLGetPortNumber(url
);
2172 CFReleaseSafe(urlStr
);
2176 #if TARGET_IPHONE_SIMULATOR
2177 secerror("app/site association entitlements not checked in Simulator");
2179 OSStatus status
= errSecMissingEntitlement
;
2181 SecError(status
, error
, CFSTR("Missing application-identifier entitlement"));
2182 CFReleaseSafe(fqdn
);
2185 // validate that fqdn is part of caller's entitlement
2186 if (_SecEntitlementContainsDomainForService(domains
, fqdn
, kSecSharedWebCredentialsService
)) {
2187 status
= errSecSuccess
;
2189 if (errSecSuccess
!= status
) {
2190 CFStringRef msg
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
,
2191 CFSTR("%@ not found in %@ entitlement"), fqdn
, kSecEntitlementAssociatedDomains
);
2193 msg
= CFRetain(CFSTR("Requested domain not found in entitlement"));
2195 SecError(status
, error
, CFSTR("%@"), msg
);
2197 CFReleaseSafe(fqdn
);
2202 attrs
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2204 SecError(errSecAllocate
, error
, CFSTR("Unable to create query dictionary"));
2205 CFReleaseSafe(fqdn
);
2208 CFDictionaryAddValue(attrs
, kSecClass
, kSecClassInternetPassword
);
2209 CFDictionaryAddValue(attrs
, kSecAttrAccessGroup
, kSecSafariAccessGroup
);
2210 CFDictionaryAddValue(attrs
, kSecAttrAuthenticationType
, kSecAttrAuthenticationTypeHTMLForm
);
2211 CFDictionaryAddValue(attrs
, kSecAttrServer
, fqdn
);
2213 CFDictionaryAddValue(attrs
, kSecAttrAccount
, account
);
2215 if (port
< -1 || port
> 0) {
2216 SInt16 portValueShort
= (port
& 0xFFFF);
2217 CFNumberRef portNumber
= CFNumberCreate(NULL
, kCFNumberSInt16Type
, &portValueShort
);
2218 CFDictionaryAddValue(attrs
, kSecAttrPort
, portNumber
);
2219 CFReleaseSafe(portNumber
);
2221 CFDictionaryAddValue(attrs
, kSecAttrSynchronizable
, kCFBooleanTrue
);
2222 CFDictionaryAddValue(attrs
, kSecMatchLimit
, kSecMatchLimitAll
);
2223 CFDictionaryAddValue(attrs
, kSecReturnAttributes
, kCFBooleanTrue
);
2224 CFDictionaryAddValue(attrs
, kSecReturnData
, kCFBooleanTrue
);
2226 ok
= _SecItemCopyMatching(attrs
, &swcclient
, (CFTypeRef
*)&items
, error
);
2228 // ignore interim error since we have multiple domains to search
2229 CFReleaseNull(*error
);
2231 if (ok
&& items
&& CFGetTypeID(items
) == CFArrayGetTypeID()) {
2232 #if TARGET_IPHONE_SIMULATOR
2233 secerror("Ignoring app/site approval state in the Simulator.");
2234 bool approved
= true;
2236 // get approval status for this app/domain pair
2237 SWCFlags flags
= _SecAppDomainApprovalStatus(appID
, fqdn
, error
);
2239 // ignore interim error since we have multiple domains to check
2240 CFReleaseNull(*error
);
2242 bool approved
= (flags
& kSWCFlag_SiteApproved
);
2245 CFArrayAppendArray(foundItems
, items
, CFRangeMake(0, CFArrayGetCount(items
)));
2248 CFReleaseSafe(items
);
2249 CFReleaseSafe(attrs
);
2250 CFReleaseSafe(fqdn
);
2253 // If matching credentials are found, the credentials provided to the completionHandler
2254 // will be a CFArrayRef containing CFDictionaryRef entries. Each dictionary entry will
2255 // contain the following pairs (see Security/SecItem.h):
2256 // key: kSecAttrServer value: CFStringRef (the website)
2257 // key: kSecAttrAccount value: CFStringRef (the account)
2258 // key: kSecSharedPassword value: CFStringRef (the password)
2260 // key: kSecAttrPort value: CFNumberRef (the port number, if non-standard for https)
2262 count
= CFArrayGetCount(foundItems
);
2263 for (idx
= 0; idx
< count
; idx
++) {
2264 CFDictionaryRef dict
= (CFDictionaryRef
) CFArrayGetValueAtIndex(foundItems
, idx
);
2265 CFMutableDictionaryRef newdict
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2266 if (newdict
&& dict
&& CFGetTypeID(dict
) == CFDictionaryGetTypeID()) {
2267 CFStringRef srvr
= CFDictionaryGetValue(dict
, kSecAttrServer
);
2268 CFStringRef acct
= CFDictionaryGetValue(dict
, kSecAttrAccount
);
2269 CFNumberRef pnum
= CFDictionaryGetValue(dict
, kSecAttrPort
);
2270 CFStringRef icmt
= CFDictionaryGetValue(dict
, kSecAttrComment
);
2271 CFDataRef data
= CFDictionaryGetValue(dict
, kSecValueData
);
2273 CFDictionaryAddValue(newdict
, kSecAttrServer
, srvr
);
2276 CFDictionaryAddValue(newdict
, kSecAttrAccount
, acct
);
2280 if (CFNumberGetValue(pnum
, kCFNumberSInt16Type
, &pval
) &&
2281 (pval
< -1 || pval
> 0)) {
2282 CFDictionaryAddValue(newdict
, kSecAttrPort
, pnum
);
2286 CFStringRef password
= CFStringCreateFromExternalRepresentation(kCFAllocatorDefault
, data
, kCFStringEncodingUTF8
);
2288 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH && !TARGET_OS_TV
2289 CFDictionaryAddValue(newdict
, kSecSharedPassword
, password
);
2291 CFDictionaryAddValue(newdict
, CFSTR("spwd"), password
);
2293 CFReleaseSafe(password
);
2296 if (icmt
&& CFEqual(icmt
, kSecSafariDefaultComment
)) {
2297 CFArrayInsertValueAtIndex(credentials
, 0, newdict
);
2299 CFArrayAppendValue(credentials
, newdict
);
2302 CFReleaseSafe(newdict
);
2309 // create a new array of dictionaries (without the actual password) for picker UI
2310 count
= CFArrayGetCount(credentials
);
2311 CFMutableArrayRef items
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
2312 for (idx
= 0; idx
< count
; idx
++) {
2313 CFDictionaryRef dict
= (CFDictionaryRef
) CFArrayGetValueAtIndex(credentials
, idx
);
2314 CFMutableDictionaryRef newdict
= CFDictionaryCreateMutableCopy(kCFAllocatorDefault
, 0, dict
);
2315 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH && !TARGET_OS_TV
2316 CFDictionaryRemoveValue(newdict
, kSecSharedPassword
);
2318 CFDictionaryRemoveValue(newdict
, CFSTR("spwd"));
2320 CFArrayAppendValue(items
, newdict
);
2321 CFReleaseSafe(newdict
);
2324 // prompt user to select one of the dictionary items
2325 CFDictionaryRef selected
= swca_copy_selected_dictionary(swca_select_request_id
,
2326 clientAuditToken
, items
, error
);
2328 // find the matching item in our credentials array
2329 CFStringRef srvr
= CFDictionaryGetValue(selected
, kSecAttrServer
);
2330 CFStringRef acct
= CFDictionaryGetValue(selected
, kSecAttrAccount
);
2331 CFNumberRef pnum
= CFDictionaryGetValue(selected
, kSecAttrPort
);
2332 for (idx
= 0; idx
< count
; idx
++) {
2333 CFDictionaryRef dict
= (CFDictionaryRef
) CFArrayGetValueAtIndex(credentials
, idx
);
2334 CFStringRef srvr1
= CFDictionaryGetValue(dict
, kSecAttrServer
);
2335 CFStringRef acct1
= CFDictionaryGetValue(dict
, kSecAttrAccount
);
2336 CFNumberRef pnum1
= CFDictionaryGetValue(dict
, kSecAttrPort
);
2338 if (!srvr
|| !srvr1
|| !CFEqual(srvr
, srvr1
)) continue;
2339 if (!acct
|| !acct1
|| !CFEqual(acct
, acct1
)) continue;
2340 if ((pnum
&& pnum1
) && !CFEqual(pnum
, pnum1
)) continue;
2343 CFReleaseSafe(selected
);
2350 CFReleaseSafe(items
);
2351 CFArrayRemoveAllValues(credentials
);
2352 if (selected
&& ok
) {
2353 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
2354 fqdn
= CFDictionaryGetValue(selected
, kSecAttrServer
);
2356 CFArrayAppendValue(credentials
, selected
);
2360 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
2361 // register confirmation with database
2362 OSStatus status
= _SecSWCEnsuredInitialized();
2364 SecError(status
, error
, CFSTR("SWC initialize failed"));
2366 CFReleaseSafe(selected
);
2369 CFRetainSafe(appID
);
2371 if (0 != sSWCSetServiceFlags_f(kSecSharedWebCredentialsService
,
2372 appID
, fqdn
, kSWCFlag_ExternalMask
, kSWCFlag_UserApproved
,
2373 ^void(OSStatus inStatus
, SWCFlags inNewFlags
){
2374 CFReleaseSafe(appID
);
2375 CFReleaseSafe(fqdn
);
2378 // we didn't queue the block
2379 CFReleaseSafe(appID
);
2380 CFReleaseSafe(fqdn
);
2384 CFReleaseSafe(selected
);
2386 else if (NULL
== *error
) {
2387 // found no items, and we haven't already filled in the error
2388 SecError(errSecItemNotFound
, error
, CFSTR("no matching items found"));
2393 CFArrayRemoveAllValues(credentials
);
2394 CFReleaseNull(credentials
);
2396 CFReleaseSafe(foundItems
);
2397 *result
= credentials
;
2398 CFReleaseSafe(swcclient
.accessGroups
);
2399 CFReleaseSafe(fqdns
);
2405 // MARK: Keychain backup
2407 CF_RETURNS_RETAINED CFDataRef
2408 _SecServerKeychainCreateBackup(SecurityClient
*client
, CFDataRef keybag
, CFDataRef passcode
, CFErrorRef
*error
) {
2410 SecDbConnectionRef dbt
= SecDbConnectionAquire(kc_dbhandle(), false, error
);
2415 if (keybag
== NULL
&& passcode
== NULL
) {
2417 backup
= SecServerExportBackupableKeychain(dbt
, client
, KEYBAG_DEVICE
, backup_keybag_handle
, error
);
2418 #else /* !USE_KEYSTORE */
2420 SecError(errSecParam
, error
, CFSTR("Why are you doing this?"));
2422 #endif /* USE_KEYSTORE */
2424 backup
= SecServerKeychainCreateBackup(dbt
, client
, keybag
, passcode
, error
);
2427 SecDbConnectionRelease(dbt
);
2433 _SecServerKeychainRestore(CFDataRef backup
, SecurityClient
*client
, CFDataRef keybag
, CFDataRef passcode
, CFErrorRef
*error
) {
2434 if (backup
== NULL
|| keybag
== NULL
)
2435 return SecError(errSecParam
, error
, CFSTR("backup or keybag missing"));
2437 __block
bool ok
= true;
2438 ok
&= SecDbPerformWrite(kc_dbhandle(), error
, ^(SecDbConnectionRef dbconn
) {
2439 ok
= SecServerKeychainRestore(dbconn
, client
, backup
, keybag
, passcode
, error
);
2443 SecKeychainChanged(true);
2450 _SecServerBackupCopyUUID(CFDataRef data
, CFErrorRef
*error
)
2452 CFStringRef uuid
= NULL
;
2453 CFDictionaryRef backup
;
2455 backup
= CFPropertyListCreateWithData(kCFAllocatorDefault
, data
,
2456 kCFPropertyListImmutable
, NULL
,
2458 if (isDictionary(backup
)) {
2459 uuid
= SecServerBackupGetKeybagUUID(backup
);
2463 CFReleaseNull(backup
);
2471 // MARK: SecItemDataSource
2473 // Make sure to call this before any writes to the keychain, so that we fire
2474 // up the engines to monitor manifest changes.
2475 SOSDataSourceFactoryRef
SecItemDataSourceFactoryGetDefault(void) {
2476 return SecItemDataSourceFactoryGetShared(kc_dbhandle());
2479 /* AUDIT[securityd]:
2480 args_in (ok) is a caller provided, CFDictionaryRef.
2483 CF_RETURNS_RETAINED CFArrayRef
2484 _SecServerKeychainSyncUpdateMessage(CFDictionaryRef updates
, CFErrorRef
*error
) {
2485 // This never fails, trust us!
2486 return SOSCCHandleUpdateMessage(updates
);
2490 // Truthiness in the cloud backup/restore support.
2493 static CFDictionaryRef
2494 _SecServerCopyTruthInTheCloud(CFDataRef keybag
, CFDataRef password
,
2495 CFDictionaryRef backup
, CFErrorRef
*error
)
2497 SOSManifestRef mold
= NULL
, mnow
= NULL
, mdelete
= NULL
, madd
= NULL
;
2498 __block CFMutableDictionaryRef backup_new
= NULL
;
2499 keybag_handle_t bag_handle
;
2500 if (!ks_open_keybag(keybag
, password
, &bag_handle
, error
))
2503 // We need to have a datasource singleton for protection domain
2504 // kSecAttrAccessibleWhenUnlocked and keep a single shared engine
2505 // instance around which we create in the datasource constructor as well.
2506 SOSDataSourceFactoryRef dsf
= SecItemDataSourceFactoryGetDefault();
2507 SOSDataSourceRef ds
= SOSDataSourceFactoryCreateDataSource(dsf
, kSecAttrAccessibleWhenUnlocked
, error
);
2509 backup_new
= backup
? CFDictionaryCreateMutableCopy(kCFAllocatorDefault
, 0, backup
) : CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2510 mold
= SOSCreateManifestWithBackup(backup
, error
);
2511 SOSEngineRef engine
= SOSDataSourceGetSharedEngine(ds
, error
);
2512 mnow
= SOSEngineCopyManifest(engine
, NULL
);
2514 mnow
= SOSDataSourceCopyManifestWithViewNameSet(ds
, SOSViewsGetV0ViewSet(), error
);
2517 CFReleaseNull(backup_new
);
2518 secerror("failed to obtain manifest for keychain: %@", error
? *error
: NULL
);
2520 SOSManifestDiff(mold
, mnow
, &mdelete
, &madd
, error
);
2523 // Delete everything from the new_backup that is no longer in the datasource according to the datasources manifest.
2524 SOSManifestForEach(mdelete
, ^(CFDataRef digest_data
, bool *stop
) {
2525 CFStringRef deleted_item_key
= CFDataCopyHexString(digest_data
);
2526 CFDictionaryRemoveValue(backup_new
, deleted_item_key
);
2527 CFRelease(deleted_item_key
);
2530 CFMutableArrayRef changes
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
2531 SOSDataSourceForEachObject(ds
, NULL
, madd
, error
, ^void(CFDataRef digest
, SOSObjectRef object
, bool *stop
) {
2532 CFErrorRef localError
= NULL
;
2533 CFDataRef digest_data
= NULL
;
2534 CFTypeRef value
= NULL
;
2536 // Key in our manifest can't be found in db, remove it from our manifest
2537 SOSChangesAppendDelete(changes
, digest
);
2538 } else if (!(digest_data
= SOSObjectCopyDigest(ds
, object
, &localError
))
2539 || !(value
= SOSObjectCopyBackup(ds
, object
, bag_handle
, &localError
))) {
2540 if (SecErrorGetOSStatus(localError
) == errSecDecode
) {
2541 // Ignore decode errors, pretend the objects aren't there
2542 CFRelease(localError
);
2543 // Object undecodable, remove it from our manifest
2544 SOSChangesAppendDelete(changes
, digest
);
2546 // Stop iterating and propagate out all other errors.
2548 *error
= localError
;
2549 CFReleaseNull(backup_new
);
2552 // TODO: Should we skip tombstones here?
2553 CFStringRef key
= CFDataCopyHexString(digest_data
);
2554 CFDictionarySetValue(backup_new
, key
, value
);
2557 CFReleaseSafe(digest_data
);
2558 CFReleaseSafe(value
);
2559 }) || CFReleaseNull(backup_new
);
2561 if (CFArrayGetCount(changes
)) {
2562 if (!SOSEngineUpdateChanges(engine
, kSOSDataSourceSOSTransaction
, changes
, error
)) {
2563 CFReleaseNull(backup_new
);
2566 CFReleaseSafe(changes
);
2568 SOSDataSourceRelease(ds
, error
) || CFReleaseNull(backup_new
);
2571 CFReleaseSafe(mold
);
2572 CFReleaseSafe(mnow
);
2573 CFReleaseSafe(madd
);
2574 CFReleaseSafe(mdelete
);
2575 ks_close_keybag(bag_handle
, error
) || CFReleaseNull(backup_new
);
2581 _SecServerRestoreTruthInTheCloud(CFDataRef keybag
, CFDataRef password
, CFDictionaryRef backup_in
, CFErrorRef
*error
) {
2582 __block
bool ok
= true;
2583 keybag_handle_t bag_handle
;
2584 if (!ks_open_keybag(keybag
, password
, &bag_handle
, error
))
2587 SOSManifestRef mbackup
= SOSCreateManifestWithBackup(backup_in
, error
);
2589 SOSDataSourceFactoryRef dsf
= SecItemDataSourceFactoryGetDefault();
2590 SOSDataSourceRef ds
= SOSDataSourceFactoryCreateDataSource(dsf
, kSecAttrAccessibleWhenUnlocked
, error
);
2591 ok
&= ds
&& SOSDataSourceWith(ds
, error
, ^(SOSTransactionRef txn
, bool *commit
) {
2592 SOSManifestRef mnow
= SOSDataSourceCopyManifestWithViewNameSet(ds
, SOSViewsGetV0BackupViewSet(), error
);
2593 SOSManifestRef mdelete
= NULL
, madd
= NULL
;
2594 SOSManifestDiff(mnow
, mbackup
, &mdelete
, &madd
, error
);
2596 // Don't delete everything in datasource not in backup.
2598 // Add items from the backup
2599 SOSManifestForEach(madd
, ^void(CFDataRef e
, bool *stop
) {
2600 CFDictionaryRef item
= NULL
;
2601 CFStringRef sha1
= CFDataCopyHexString(e
);
2603 item
= CFDictionaryGetValue(backup_in
, sha1
);
2607 CFErrorRef localError
= NULL
;
2609 if (!SOSObjectRestoreObject(ds
, txn
, bag_handle
, item
, &localError
)) {
2610 OSStatus status
= SecErrorGetOSStatus(localError
);
2611 if (status
== errSecDuplicateItem
) {
2612 // Log and ignore duplicate item errors during restore
2613 secnotice("titc", "restore %@ not replacing existing item", item
);
2614 } else if (status
== errSecDecode
) {
2615 // Log and ignore corrupted item errors during restore
2616 secnotice("titc", "restore %@ skipping corrupted item %@", item
, localError
);
2618 if (status
== errSecInteractionNotAllowed
)
2620 // Propagate the first other error upwards (causing the restore to fail).
2621 secerror("restore %@ failed %@", item
, localError
);
2623 if (error
&& !*error
) {
2624 *error
= localError
;
2628 CFReleaseSafe(localError
);
2632 ok
&= SOSDataSourceRelease(ds
, error
);
2633 CFReleaseNull(mdelete
);
2634 CFReleaseNull(madd
);
2635 CFReleaseNull(mnow
);
2640 ok
&= ks_close_keybag(bag_handle
, error
);
2646 CF_RETURNS_RETAINED CFDictionaryRef
2647 _SecServerBackupSyncable(CFDictionaryRef backup
, CFDataRef keybag
, CFDataRef password
, CFErrorRef
*error
) {
2648 require_action_quiet(isData(keybag
), errOut
, SecError(errSecParam
, error
, CFSTR("keybag %@ not a data"), keybag
));
2649 require_action_quiet(!backup
|| isDictionary(backup
), errOut
, SecError(errSecParam
, error
, CFSTR("backup %@ not a dictionary"), backup
));
2650 require_action_quiet(!password
|| isData(password
), errOut
, SecError(errSecParam
, error
, CFSTR("password %@ not a data"), password
));
2652 return _SecServerCopyTruthInTheCloud(keybag
, password
, backup
, error
);
2659 _SecServerRestoreSyncable(CFDictionaryRef backup
, CFDataRef keybag
, CFDataRef password
, CFErrorRef
*error
) {
2661 require_action_quiet(isData(keybag
), errOut
, ok
= SecError(errSecParam
, error
, CFSTR("keybag %@ not a data"), keybag
));
2662 require_action_quiet(isDictionary(backup
), errOut
, ok
= SecError(errSecParam
, error
, CFSTR("backup %@ not a dictionary"), backup
));
2665 require_action_quiet(isData(password
), errOut
, ok
= SecError(errSecParam
, error
, CFSTR("password not a data")));
2668 ok
= _SecServerRestoreTruthInTheCloud(keybag
, password
, backup
, error
);
2674 bool _SecServerRollKeysGlue(bool force
, CFErrorRef
*error
) {
2675 return _SecServerRollKeys(force
, NULL
, error
);
2679 bool _SecServerRollKeys(bool force
, SecurityClient
*client
, CFErrorRef
*error
) {
2681 uint32_t keystore_generation_status
= 0;
2682 if (aks_generation(KEYBAG_DEVICE
, generation_noop
, &keystore_generation_status
))
2684 uint32_t current_generation
= keystore_generation_status
& generation_current
;
2686 return kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
2687 bool up_to_date
= s3dl_dbt_keys_current(dbt
, current_generation
, NULL
);
2689 if (force
&& !up_to_date
) {
2690 up_to_date
= s3dl_dbt_update_keys(dbt
, client
, error
);
2692 secerror("Completed roll keys.");
2693 up_to_date
= s3dl_dbt_keys_current(dbt
, current_generation
, NULL
);
2696 secerror("Failed to roll keys.");
2708 * Sync bubble migration code
2711 struct SyncBubbleRule
{
2712 CFStringRef attribute
;
2717 TransmogrifyItemsToSyncBubble(SecurityClient
*client
, uid_t uid
,
2720 const SecDbClass
*qclass
,
2721 struct SyncBubbleRule
*items
, CFIndex nItems
,
2724 CFMutableDictionaryRef updateAttributes
= NULL
;
2725 CFDataRef syncBubbleView
= NULL
;
2726 CFDataRef activeUserView
= NULL
;
2731 syncBubbleView
= SecMUSRCreateSyncBubbleUserUUID(uid
);
2732 require(syncBubbleView
, fail
);
2734 activeUserView
= SecMUSRCreateActiveUserUUID(uid
);
2735 require(activeUserView
, fail
);
2738 if ((onlyDelete
&& !copyToo
) || !onlyDelete
) {
2741 * Clean out items first
2744 secnotice("syncbubble", "cleaning out old items");
2746 q
= query_create(qclass
, NULL
, NULL
, error
);
2749 q
->q_limit
= kSecMatchUnlimited
;
2750 q
->q_keybag
= device_keybag_handle
;
2752 for (n
= 0; n
< nItems
; n
++) {
2753 query_add_attribute(items
[n
].attribute
, items
[n
].value
, q
);
2755 q
->q_musrView
= CFRetain(syncBubbleView
);
2756 require(q
->q_musrView
, fail
);
2758 kc_with_dbt(false, error
, ^(SecDbConnectionRef dbt
) {
2759 return kc_transaction(dbt
, error
, ^{
2760 return s3dl_query_delete(dbt
, q
, NULL
, error
);
2764 query_destroy(q
, NULL
);
2769 if (onlyDelete
|| !copyToo
) {
2770 secnotice("syncbubble", "skip migration of items");
2773 * Copy over items from EMCS to sync bubble
2776 secnotice("syncbubble", "migrating sync bubble items");
2778 q
= query_create(qclass
, NULL
, NULL
, error
);
2781 q
->q_return_type
= kSecReturnDataMask
| kSecReturnAttributesMask
;
2782 q
->q_limit
= kSecMatchUnlimited
;
2783 q
->q_keybag
= device_keybag_handle
; /* XXX change to session key bag when it exists */
2785 for (n
= 0; n
< nItems
; n
++) {
2786 query_add_or_attribute(items
[n
].attribute
, items
[n
].value
, q
);
2788 query_add_or_attribute(CFSTR("musr"), activeUserView
, q
);
2789 q
->q_musrView
= CFRetain(activeUserView
);
2791 updateAttributes
= CFDictionaryCreateMutableForCFTypes(NULL
);
2792 require(updateAttributes
, fail
);
2794 CFDictionarySetValue(updateAttributes
, CFSTR("musr"), syncBubbleView
); /* XXX should use kSecAttrMultiUser */
2797 kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
2798 return kc_transaction(dbt
, error
, ^{
2799 CFErrorRef error2
= NULL
;
2801 SecDbItemSelect(q
, dbt
, &error2
, NULL
, ^bool(const SecDbAttr
*attr
) {
2802 return CFDictionaryGetValue(q
->q_item
, attr
->name
);
2803 }, NULL
, NULL
, ^(SecDbItemRef item
, bool *stop
) {
2804 CFErrorRef error3
= NULL
;
2805 secinfo("syncbubble", "migrating item");
2807 SecDbItemRef new_item
= SecDbItemCopyWithUpdates(item
, updateAttributes
, NULL
);
2808 if (new_item
== NULL
)
2811 SecDbItemClearRowId(new_item
, NULL
);
2813 if (!SecDbItemSetKeybag(new_item
, device_keybag_handle
, NULL
)) {
2814 CFRelease(new_item
);
2818 if (!SecDbItemInsert(new_item
, dbt
, &error3
)) {
2819 secnotice("syncbubble", "migration failed with %@ for item %@", error3
, new_item
);
2821 CFRelease(new_item
);
2822 CFReleaseNull(error3
);
2824 CFReleaseNull(error2
);
2833 CFReleaseNull(syncBubbleView
);
2834 CFReleaseNull(activeUserView
);
2835 CFReleaseNull(updateAttributes
);
2837 query_destroy(q
, NULL
);
2842 static struct SyncBubbleRule PCSItems
[] = {
2844 .attribute
= CFSTR("agrp"),
2845 .value
= CFSTR("com.apple.ProtectedCloudStorage"),
2848 static struct SyncBubbleRule NSURLSesssiond
[] = {
2850 .attribute
= CFSTR("agrp"),
2851 .value
= CFSTR("com.apple.nsurlsessiond"),
2854 static struct SyncBubbleRule AccountsdItems
[] = {
2856 .attribute
= CFSTR("svce"),
2857 .value
= CFSTR("com.apple.account.AppleAccount.token"),
2860 .attribute
= CFSTR("svce"),
2861 .value
= CFSTR("com.apple.account.AppleAccount.password"),
2864 .attribute
= CFSTR("svce"),
2865 .value
= CFSTR("com.apple.account.AppleAccount.rpassword"),
2868 .attribute
= CFSTR("svce"),
2869 .value
= CFSTR("com.apple.account.idms.token"),
2872 .attribute
= CFSTR("svce"),
2873 .value
= CFSTR("com.apple.account.idms.continuation-key"),
2876 .attribute
= CFSTR("svce"),
2877 .value
= CFSTR("com.apple.account.CloudKit.token"),
2881 static struct SyncBubbleRule MobileMailItems
[] = {
2883 .attribute
= CFSTR("svce"),
2884 .value
= CFSTR("com.apple.account.IMAP.password"),
2887 .attribute
= CFSTR("svce"),
2888 .value
= CFSTR("com.apple.account.SMTP.password"),
2891 .attribute
= CFSTR("svce"),
2892 .value
= CFSTR("com.apple.account.Exchange.password"),
2895 .attribute
= CFSTR("svce"),
2896 .value
= CFSTR("com.apple.account.Hotmail.password"),
2899 .attribute
= CFSTR("svce"),
2900 .value
= CFSTR("com.apple.account.Google.password"),
2903 .attribute
= CFSTR("svce"),
2904 .value
= CFSTR("com.apple.account.Google.oauth-token"),
2907 .attribute
= CFSTR("svce"),
2908 .value
= CFSTR("com.apple.account.Google.oath-refresh-token"),
2911 .attribute
= CFSTR("svce"),
2912 .value
= CFSTR("com.apple.account.Yahoo.password"),
2915 .attribute
= CFSTR("svce"),
2916 .value
= CFSTR("com.apple.account.Yahoo.oauth-token"),
2919 .attribute
= CFSTR("svce"),
2920 .value
= CFSTR("com.apple.account.Yahoo.oauth-token-nosync"),
2923 .attribute
= CFSTR("svce"),
2924 .value
= CFSTR("com.apple.account.Yahoo.oath-refresh-token"),
2927 .attribute
= CFSTR("svce"),
2928 .value
= CFSTR("com.apple.account.IMAPNotes.password"),
2931 .attribute
= CFSTR("svce"),
2932 .value
= CFSTR("com.apple.account.IMAPMail.password"),
2935 .attribute
= CFSTR("svce"),
2936 .value
= CFSTR("com.apple.account.126.password"),
2939 .attribute
= CFSTR("svce"),
2940 .value
= CFSTR("com.apple.account.163.password"),
2943 .attribute
= CFSTR("svce"),
2944 .value
= CFSTR("com.apple.account.aol.password"),
2949 ArrayContains(CFArrayRef array
, CFStringRef service
)
2951 return CFArrayContainsValue(array
, CFRangeMake(0, CFArrayGetCount(array
)), service
);
2955 _SecServerTransmogrifyToSyncBubble(CFArrayRef services
, uid_t uid
, SecurityClient
*client
, CFErrorRef
*error
)
2957 bool copyCloudAuthToken
= false;
2958 bool copyMobileMail
= false;
2960 bool copyPCS
= false;
2961 bool onlyDelete
= false;
2962 bool copyNSURLSesssion
= false;
2964 if (!client
->inMultiUser
)
2967 secnotice("syncbubble", "migration for uid %d uid for services %@", (int)uid
, services
);
2969 #if TARGET_OS_SIMULATOR
2972 if (uid
!= (uid_t
)client
->activeUser
)
2975 #error "no sync bubble on other platforms"
2979 * First select that services to copy/delete
2982 if (ArrayContains(services
, CFSTR("com.apple.bird.usermanager.sync"))
2983 || ArrayContains(services
, CFSTR("com.apple.cloudphotod.sync"))
2984 || ArrayContains(services
, CFSTR("com.apple.cloudphotod.syncstakeholder"))
2985 || ArrayContains(services
, CFSTR("com.apple.cloudd.usermanager.sync")))
2987 copyCloudAuthToken
= true;
2991 if (ArrayContains(services
, CFSTR("com.apple.nsurlsessiond.usermanager.sync")))
2993 copyCloudAuthToken
= true;
2994 copyNSURLSesssion
= true;
2997 if (ArrayContains(services
, CFSTR("com.apple.syncdefaultsd.usermanager.sync"))) {
2998 copyCloudAuthToken
= true;
3000 if (ArrayContains(services
, CFSTR("com.apple.mailq.sync")) || ArrayContains(services
, CFSTR("com.apple.mailq.sync.xpc"))) {
3001 copyCloudAuthToken
= true;
3002 copyMobileMail
= true;
3007 * The actually copy/delete the items selected
3010 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyPCS
, &inet_class
, PCSItems
, sizeof(PCSItems
)/sizeof(PCSItems
[0]), error
);
3012 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyPCS
, &genp_class
, PCSItems
, sizeof(PCSItems
)/sizeof(PCSItems
[0]), error
);
3016 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyMobileMail
, &genp_class
, MobileMailItems
, sizeof(MobileMailItems
)/sizeof(MobileMailItems
[0]), error
);
3020 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyCloudAuthToken
, &genp_class
, AccountsdItems
, sizeof(AccountsdItems
)/sizeof(AccountsdItems
[0]), error
);
3024 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyNSURLSesssion
, &inet_class
, NSURLSesssiond
, sizeof(NSURLSesssiond
)/sizeof(NSURLSesssiond
[0]), error
);
3032 * Migrate from user keychain to system keychain when switching to edu mode
3036 _SecServerTransmogrifyToSystemKeychain(SecurityClient
*client
, CFErrorRef
*error
)
3038 __block
bool ok
= true;
3041 * we are not in multi user yet, about to switch, otherwise we would
3042 * check that for client->inMultiuser here
3045 kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
3046 return kc_transaction(dbt
, error
, ^{
3047 CFDataRef systemUUID
= SecMUSRGetSystemKeychainUUID();
3049 const SecDbSchema
*newSchema
= kc_schemas
[0];
3050 SecDbClass
const *const *kcClass
;
3052 for (kcClass
= newSchema
->classes
; *kcClass
!= NULL
; kcClass
++) {
3053 CFErrorRef localError
= NULL
;
3056 if (*kcClass
== &tversion_class
|| *kcClass
== &identity_class
)
3059 q
= query_create(*kcClass
, SecMUSRGetSingleUserKeychainUUID(), NULL
, error
);
3063 ok
&= SecDbItemSelect(q
, dbt
, error
, ^bool(const SecDbAttr
*attr
) {
3064 return (attr
->flags
& kSecDbInFlag
) != 0;
3065 }, ^bool(const SecDbAttr
*attr
) {
3066 // No filtering please.
3068 }, ^bool(CFMutableStringRef sql
, bool *needWhere
) {
3069 SecDbAppendWhereOrAnd(sql
, needWhere
);
3070 CFStringAppendFormat(sql
, NULL
, CFSTR("musr = ?"));
3072 }, ^bool(sqlite3_stmt
*stmt
, int col
) {
3073 return SecDbBindObject(stmt
, col
++, SecMUSRGetSingleUserKeychainUUID(), error
);
3074 }, ^(SecDbItemRef item
, bool *stop
) {
3075 CFErrorRef localError
= NULL
;
3077 if (!SecDbItemSetValueWithName(item
, kSecAttrMultiUser
, systemUUID
, &localError
)) {
3078 secerror("item: %@ update musr to system failed: %@", item
, localError
);
3083 if (!SecDbItemDoUpdate(item
, item
, dbt
, &localError
, ^bool (const SecDbAttr
*attr
) {
3084 return attr
->kind
== kSecDbRowIdAttr
;
3086 secerror("item: %@ insert during UPDATE: %@", item
, localError
);
3092 SecErrorPropagate(localError
, error
);
3093 CFReleaseSafe(localError
);
3097 query_destroy(q
, &localError
);
3108 * Delete account from local usage
3112 _SecServerDeleteMUSERViews(SecurityClient
*client
, uid_t uid
, CFErrorRef
*error
)
3114 return kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
3115 CFDataRef musrView
= NULL
, syncBubbleView
= NULL
;
3118 syncBubbleView
= SecMUSRCreateSyncBubbleUserUUID(uid
);
3119 require(syncBubbleView
, fail
);
3121 musrView
= SecMUSRCreateActiveUserUUID(uid
);
3122 require(musrView
, fail
);
3124 require(ok
= SecServerDeleteAllForUser(dbt
, syncBubbleView
, false, error
), fail
);
3125 require(ok
= SecServerDeleteAllForUser(dbt
, musrView
, false, error
), fail
);
3128 CFReleaseNull(syncBubbleView
);
3129 CFReleaseNull(musrView
);
3135 #endif /* TARGET_OS_IOS */
3138 _SecServerGetKeyStats(const SecDbClass
*qclass
,
3139 struct _SecServerKeyStats
*stats
)
3141 __block CFErrorRef error
= NULL
;
3144 Query
*q
= query_create(qclass
, NULL
, NULL
, &error
);
3147 q
->q_return_type
= kSecReturnDataMask
| kSecReturnAttributesMask
;
3148 q
->q_limit
= kSecMatchUnlimited
;
3149 q
->q_keybag
= KEYBAG_DEVICE
;
3150 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleWhenUnlocked
, q
);
3151 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleAfterFirstUnlock
, q
);
3152 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleAlways
, q
);
3153 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleWhenUnlockedThisDeviceOnly
, q
);
3154 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
, q
);
3155 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleAlwaysThisDeviceOnly
, q
);
3156 query_add_attribute(kSecAttrTombstone
, kCFBooleanFalse
, q
);
3158 kc_with_dbt(false, &error
, ^(SecDbConnectionRef dbconn
) {
3159 CFErrorRef error2
= NULL
;
3160 __block CFIndex totalSize
= 0;
3161 stats
->maxDataSize
= 0;
3163 SecDbItemSelect(q
, dbconn
, &error2
, NULL
, ^bool(const SecDbAttr
*attr
) {
3164 return CFDictionaryContainsKey(q
->q_item
, attr
->name
);
3165 }, NULL
, NULL
, ^(SecDbItemRef item
, bool *stop
) {
3166 CFErrorRef error3
= NULL
;
3167 CFDataRef data
= SecDbItemGetValue(item
, &v6v_Data
, &error3
);
3169 CFIndex size
= CFDataGetLength(data
);
3170 if (size
> stats
->maxDataSize
)
3171 stats
->maxDataSize
= size
;
3175 CFReleaseNull(error3
);
3177 CFReleaseNull(error2
);
3179 stats
->averageSize
= totalSize
/ stats
->items
;
3188 CFReleaseNull(error
);
3190 query_destroy(q
, NULL
);