2 * Copyright (c) 2006-2015 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
21 * @APPLE_LICENSE_HEADER_END@
25 * SecItemServer.c - CoreFoundation-based constants and functions for
26 access to Security items (certificates, keys, identities, and
30 #include <securityd/SecItemServer.h>
33 #include <securityd/SecItemDataSource.h>
34 #include <securityd/SecItemDb.h>
35 #include <securityd/SecItemSchema.h>
36 #include <securityd/SOSCloudCircleServer.h>
37 #include <Security/SecBasePriv.h>
38 #include <Security/SecItemPriv.h>
39 #include <Security/SecItemInternal.h>
40 #include <Security/SecureObjectSync/SOSChangeTracker.h>
41 #include <Security/SecureObjectSync/SOSDigestVector.h>
42 #include <Security/SecureObjectSync/SOSViews.h>
43 #include <Security/SecTrustPriv.h>
44 #include <Security/SecTrustInternal.h>
45 #include <Security/SecCertificatePriv.h>
48 #include <MobileKeyBag/MobileKeyBag.h>
50 // TODO: Make this include work on both platforms. rdar://problem/16526848
51 #if TARGET_OS_EMBEDDED
52 #include <Security/SecEntitlements.h>
54 /* defines from <Security/SecEntitlements.h> */
55 #define kSecEntitlementAssociatedDomains CFSTR("com.apple.developer.associated-domains")
56 #define kSecEntitlementPrivateAssociatedDomains CFSTR("com.apple.private.associated-domains")
59 #if TARGET_OS_EMBEDDED
60 #include <AggregateDictionary/ADClient.h>
65 #include <utilities/array_size.h>
66 #include <utilities/SecFileLocations.h>
67 #include <utilities/SecTrace.h>
68 #include <utilities/SecXPCError.h>
69 #include <Security/SecuritydXPC.h>
70 #include "swcagent_client.h"
72 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
74 #include <SharedWebCredentials/SharedWebCredentials.h>
76 typedef OSStatus (*SWCCheckService_f
)(CFStringRef service
, CFStringRef appID
, CFStringRef domain
, SWCCheckServiceCompletion_b completion
);
77 typedef OSStatus (*SWCSetServiceFlags_f
)(CFStringRef service
, CFStringRef appID
, CFStringRef domain
, SWCFlags mask
, SWCFlags flags
, SWCSetServiceFlagsCompletion_b completion
);
79 typedef uint32_t SWCFlags
;
80 #define kSWCFlags_None 0
81 #define kSWCFlag_Pending ( 1U << 0 )
82 #define kSWCFlag_SiteApproved ( 1U << 1 )
83 #define kSWCFlag_SiteDenied ( 1U << 2 )
84 #define kSWCFlag_UserApproved ( 1U << 3 )
85 #define kSWCFlag_UserDenied ( 1U << 4 )
86 #define kSWCFlag_ExternalMask ( kSWCFlag_UserApproved | kSWCFlag_UserDenied )
89 /* Changed the name of the keychain changed notification, for testing */
90 static const char *g_keychain_changed_notification
= kSecServerKeychainChangedNotification
;
92 void SecItemServerSetKeychainChangedNotification(const char *notification_name
)
94 g_keychain_changed_notification
= notification_name
;
97 void SecKeychainChanged() {
98 uint32_t result
= notify_post(g_keychain_changed_notification
);
99 if (result
== NOTIFY_STATUS_OK
)
100 secnotice("item", "Sent %s", g_keychain_changed_notification
);
102 secerror("notify_post %s returned: %" PRIu32
, 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
|| (code
& 0xff) == SQLITE_FULL
))
608 // TODO This should not be true but SecDb code is too eager to corrupt, rdar://problem/29771874
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"));
717 * Similar to ks_open_keybag, but goes through MKB interface
719 static bool mkb_open_keybag(CFDataRef keybag
, CFDataRef password
, MKBKeyBagHandleRef
*handle
, CFErrorRef
*error
) {
721 MKBKeyBagHandleRef mkbhandle
= NULL
;
723 rc
= MKBKeyBagCreateWithData(keybag
, &mkbhandle
);
724 if (rc
!= kMobileKeyBagSuccess
) {
725 return SecKernError(rc
, error
, CFSTR("MKBKeyBagCreateWithData failed: %d"), rc
);
729 rc
= MKBKeyBagUnlock(mkbhandle
, password
);
730 if (rc
!= kMobileKeyBagSuccess
) {
731 CFRelease(mkbhandle
);
732 return SecKernError(rc
, error
, CFSTR("failed to unlock bag: %d"), rc
);
743 static CFDataRef
SecServerKeychainCreateBackup(SecDbConnectionRef dbt
, SecurityClient
*client
, CFDataRef keybag
,
744 CFDataRef password
, CFErrorRef
*error
) {
745 CFDataRef backup
= NULL
;
746 keybag_handle_t backup_keybag
;
748 MKBKeyBagHandleRef mkbhandle
= NULL
;
749 require(mkb_open_keybag(keybag
, password
, &mkbhandle
, error
), out
);
751 require_noerr(MKBKeyBagGetAKSHandle(mkbhandle
, &backup_keybag
), out
);
754 backup_keybag
= KEYBAG_NONE
;
756 /* Export from system keybag to backup keybag. */
757 backup
= SecServerExportBackupableKeychain(dbt
, client
, KEYBAG_DEVICE
, backup_keybag
, error
);
762 CFRelease(mkbhandle
);
767 static bool SecServerKeychainRestore(SecDbConnectionRef dbt
,
768 SecurityClient
*client
,
775 keybag_handle_t backup_keybag
;
777 MKBKeyBagHandleRef mkbhandle
= NULL
;
778 require(mkb_open_keybag(keybag
, password
, &mkbhandle
, error
), out
);
780 require_noerr(MKBKeyBagGetAKSHandle(mkbhandle
, &backup_keybag
), out
);
782 backup_keybag
= KEYBAG_NONE
;
784 /* Import from backup keybag to system keybag. */
785 require(SecServerImportBackupableKeychain(dbt
, client
, backup_keybag
, KEYBAG_DEVICE
, backup
, error
), out
);
791 CFRelease(mkbhandle
);
797 // MARK - External SPI support code.
799 CFStringRef
__SecKeychainCopyPath(void) {
800 CFStringRef kcRelPath
= NULL
;
802 kcRelPath
= CFSTR("keychain-2.db");
804 kcRelPath
= CFSTR("keychain-2-debug.db");
807 CFStringRef kcPath
= NULL
;
808 CFURLRef kcURL
= SecCopyURLForFileInKeychainDirectory(kcRelPath
);
810 kcPath
= CFURLCopyFileSystemPath(kcURL
, kCFURLPOSIXPathStyle
);
817 // MARK: kc_dbhandle init and reset
819 SecDbRef
SecKeychainDbCreate(CFStringRef path
) {
820 return SecDbCreate(path
, ^bool (SecDbConnectionRef dbconn
, bool didCreate
, bool *callMeAgainForNextConnection
, CFErrorRef
*error
) {
821 // Upgrade from version 0 means create the schema in empty db.
825 ok
= SecKeychainDbGetVersion(dbconn
, &version
, error
);
827 ok
= ok
&& SecKeychainDbUpgradeFromVersion(dbconn
, version
, callMeAgainForNextConnection
, error
);
829 secerror("Upgrade %sfailed: %@", didCreate
? "from v0 " : "", error
? *error
: NULL
);
835 static SecDbRef _kc_dbhandle
= NULL
;
837 static void kc_dbhandle_init(void) {
838 SecDbRef oldHandle
= _kc_dbhandle
;
840 CFStringRef dbPath
= __SecKeychainCopyPath();
842 _kc_dbhandle
= SecKeychainDbCreate(dbPath
);
845 secerror("no keychain path available");
848 secerror("replaced %@ with %@", oldHandle
, _kc_dbhandle
);
849 CFRelease(oldHandle
);
853 // A callback for the sqlite3_log() interface.
854 static void sqlite3Log(void *pArg
, int iErrCode
, const char *zMsg
){
855 secdebug("sqlite3", "(%d) %s", iErrCode
, zMsg
);
859 _SecServerDatabaseSetup(void)
861 static dispatch_once_t onceToken
;
862 dispatch_once(&onceToken
, ^{
863 int rx
= sqlite3_config(SQLITE_CONFIG_LOG
, sqlite3Log
, NULL
);
864 if (SQLITE_OK
!= rx
) {
865 secwarning("Could not set up sqlite global error logging to syslog: %d", rx
);
870 static SecDbRef
kc_dbhandle(void)
872 static dispatch_once_t onceToken
;
873 dispatch_once(&onceToken
, ^{
874 _SecServerDatabaseSetup();
880 /* For whitebox testing only */
881 void SecKeychainDbReset(dispatch_block_t inbetween
)
883 CFStringRef dbPath
= __SecKeychainCopyPath();
887 CFReleaseNull(_kc_dbhandle
);
892 _kc_dbhandle
= SecKeychainDbCreate(dbPath
);
896 static SecDbConnectionRef
kc_acquire_dbt(bool writeAndRead
, CFErrorRef
*error
) {
897 SecDbRef db
= kc_dbhandle();
899 SecError(errSecDataNotAvailable
, error
, CFSTR("failed to get a db handle"));
902 return SecDbConnectionAcquire(db
, !writeAndRead
, error
);
905 /* Return a per thread dbt handle for the keychain. If create is true create
906 the database if it does not yet exist. If it is false, just return an
907 error if it fails to auto-create. */
908 static bool kc_with_dbt(bool writeAndRead
, CFErrorRef
*error
, bool (^perform
)(SecDbConnectionRef dbt
))
910 // Make sure we initialize our engines before writing to the keychain
912 SecItemDataSourceFactoryGetDefault();
915 SecDbConnectionRef dbt
= kc_acquire_dbt(writeAndRead
, error
);
918 SecDbConnectionRelease(dbt
);
924 items_matching_issuer_parent(SecDbConnectionRef dbt
, CFArrayRef accessGroups
, CFDataRef musrView
,
925 CFDataRef issuer
, CFArrayRef issuers
, int recurse
)
928 CFArrayRef results
= NULL
;
932 if (CFArrayContainsValue(issuers
, CFRangeMake(0, CFArrayGetCount(issuers
)), issuer
))
935 /* XXX make musr supported */
936 const void *keys
[] = { kSecClass
, kSecReturnRef
, kSecAttrSubject
};
937 const void *vals
[] = { kSecClassCertificate
, kCFBooleanTrue
, issuer
};
938 CFDictionaryRef query
= CFDictionaryCreate(kCFAllocatorDefault
, keys
, vals
, array_size(keys
), NULL
, NULL
);
943 CFErrorRef localError
= NULL
;
944 q
= query_create_with_limit(query
, musrView
, kSecMatchUnlimited
, &localError
);
947 s3dl_copy_matching(dbt
, q
, (CFTypeRef
*)&results
, accessGroups
, &localError
);
948 query_destroy(q
, &localError
);
951 secerror("items matching issuer parent: %@", localError
);
952 CFReleaseNull(localError
);
956 count
= CFArrayGetCount(results
);
957 for (i
= 0; (i
< count
) && !found
; i
++) {
958 CFDictionaryRef cert_dict
= (CFDictionaryRef
)CFArrayGetValueAtIndex(results
, i
);
959 CFDataRef cert_issuer
= CFDictionaryGetValue(cert_dict
, kSecAttrIssuer
);
960 if (CFEqual(cert_issuer
, issuer
))
963 found
= items_matching_issuer_parent(dbt
, accessGroups
, musrView
, cert_issuer
, issuers
, recurse
);
965 CFReleaseSafe(results
);
971 _FilterWithPolicy(SecPolicyRef policy
, CFDateRef date
, SecCertificateRef cert
)
973 CFDictionaryRef props
= NULL
;
974 CFArrayRef keychains
= NULL
;
975 CFArrayRef anchors
= NULL
;
976 CFArrayRef certs
= NULL
;
977 CFArrayRef chain
= NULL
;
978 SecTrustRef trust
= NULL
;
980 SecTrustResultType trustResult
;
981 Boolean needChain
= false;
982 __block
bool ok
= false;
984 if (!policy
|| !cert
) return false;
986 certs
= CFArrayCreate(NULL
, (const void **)&cert
, (CFIndex
)1, &kCFTypeArrayCallBacks
);
987 require_noerr_quiet(SecTrustCreateWithCertificates(certs
, policy
, &trust
), cleanup
);
989 /* Set evaluation date, if specified (otherwise current date is implied) */
990 if (date
&& (CFGetTypeID(date
) == CFDateGetTypeID())) {
991 require_noerr_quiet(SecTrustSetVerifyDate(trust
, date
), cleanup
);
994 /* Check whether this is the X509 Basic policy, which means chain building */
995 props
= SecPolicyCopyProperties(policy
);
997 CFTypeRef oid
= (CFTypeRef
) CFDictionaryGetValue(props
, kSecPolicyOid
);
998 if (oid
&& (CFEqual(oid
, kSecPolicyAppleX509Basic
) ||
999 CFEqual(oid
, kSecPolicyAppleRevocation
))) {
1005 require_noerr_quiet(SecTrustEvaluateLeafOnly(trust
, &trustResult
), cleanup
);
1007 require_noerr_quiet(SecTrustEvaluate(trust
, &trustResult
), cleanup
);
1010 require_quiet((trustResult
== kSecTrustResultProceed
||
1011 trustResult
== kSecTrustResultUnspecified
||
1012 trustResult
== kSecTrustResultRecoverableTrustFailure
), cleanup
);
1015 #if TARGET_OS_IPHONE
1016 CFArrayRef properties
= SecTrustCopyProperties(trust
);
1018 CFArrayRef properties
= SecTrustCopyProperties_ios(trust
);
1021 CFArrayForEach(properties
, ^(const void *property
) {
1022 CFDictionaryForEach((CFDictionaryRef
)property
, ^(const void *key
, const void *value
) {
1023 if (CFEqual((CFTypeRef
)key
, kSecPropertyKeyType
) && CFEqual((CFTypeRef
)value
, kSecPropertyTypeError
))
1027 CFRelease(properties
);
1031 if(props
) CFRelease(props
);
1032 if(chain
) CFRelease(chain
);
1033 if(anchors
) CFRelease(anchors
);
1034 if(keychains
) CFRelease(keychains
);
1035 if(certs
) CFRelease(certs
);
1036 if(trust
) CFRelease(trust
);
1042 _FilterWithDate(CFDateRef validOnDate
, SecCertificateRef cert
)
1044 if (!validOnDate
|| !cert
) return false;
1046 CFAbsoluteTime at
, nb
, na
;
1047 at
= CFDateGetAbsoluteTime((CFDateRef
)validOnDate
);
1050 nb
= SecCertificateNotValidBefore(cert
);
1051 na
= SecCertificateNotValidAfter(cert
);
1053 if (nb
== 0 || na
== 0 || nb
== na
) {
1055 secnotice("FilterWithDate", "certificate cannot operate");
1059 secnotice("FilterWithDate", "certificate is not valid yet");
1063 secnotice("FilterWithDate", "certificate expired");
1070 _FilterWithTrust(Boolean trustedOnly
, SecCertificateRef cert
)
1072 if (!cert
) return false;
1073 if (!trustedOnly
) return true;
1076 CFArrayRef certArray
= CFArrayCreate(NULL
, (const void**)&cert
, 1, &kCFTypeArrayCallBacks
);
1077 SecTrustRef trust
= NULL
;
1078 SecPolicyRef policy
= SecPolicyCreateBasicX509();
1079 require_quiet(policy
, out
);
1081 require_noerr_quiet(SecTrustCreateWithCertificates(certArray
, policy
, &trust
), out
);
1082 SecTrustResultType trustResult
;
1083 require_noerr_quiet(SecTrustEvaluate(trust
, &trustResult
), out
);
1085 require_quiet((trustResult
== kSecTrustResultProceed
||
1086 trustResult
== kSecTrustResultUnspecified
), out
);
1089 CFReleaseSafe(trust
);
1090 CFReleaseSafe(policy
);
1091 CFReleaseSafe(certArray
);
1095 static SecCertificateRef
1096 CopyCertificateFromItem(Query
*q
, CFDictionaryRef item
) {
1097 SecCertificateRef certRef
= NULL
;
1099 CFTypeRef tokenID
= NULL
;
1100 CFDataRef certData
= NULL
;
1101 if (q
->q_class
== &identity_class
) {
1102 certData
= CFDictionaryGetValue(item
, kSecAttrIdentityCertificateData
);
1103 tokenID
= CFDictionaryGetValue(item
, kSecAttrIdentityCertificateTokenID
);
1104 } else if (q
->q_class
== &cert_class
) {
1105 certData
= CFDictionaryGetValue(item
, kSecValueData
);
1106 tokenID
= CFDictionaryGetValue(item
, kSecAttrTokenID
);
1109 require_quiet(certData
, out
);
1110 if (tokenID
!= NULL
) {
1111 CFErrorRef error
= NULL
;
1112 CFDataRef tokenCertData
= _SecTokenItemCopyValueData(certData
, &error
);
1113 require_action_quiet(tokenCertData
, out
, { secerror("function _SecTokenItemCopyValueData failed with: %@", error
); CFReleaseSafe(error
); });
1114 certRef
= SecCertificateCreateWithData(kCFAllocatorDefault
, tokenCertData
);
1115 CFRelease(tokenCertData
);
1118 certRef
= SecCertificateCreateWithData(kCFAllocatorDefault
, certData
);
1124 bool match_item(SecDbConnectionRef dbt
, Query
*q
, CFArrayRef accessGroups
, CFDictionaryRef item
)
1127 SecCertificateRef certRef
= NULL
;
1128 if (q
->q_match_issuer
) {
1129 CFDataRef issuer
= CFDictionaryGetValue(item
, kSecAttrIssuer
);
1130 if (!items_matching_issuer_parent(dbt
, accessGroups
, q
->q_musrView
, issuer
, q
->q_match_issuer
, 10 /*max depth*/))
1134 if (q
->q_match_policy
&& (q
->q_class
== &identity_class
|| q
->q_class
== &cert_class
)) {
1136 certRef
= CopyCertificateFromItem(q
, item
);
1137 require_quiet(certRef
, out
);
1138 require_quiet(_FilterWithPolicy(q
->q_match_policy
, q
->q_match_valid_on_date
, certRef
), out
);
1141 if (q
->q_match_valid_on_date
&& (q
->q_class
== &identity_class
|| q
->q_class
== &cert_class
)) {
1143 certRef
= CopyCertificateFromItem(q
, item
);
1144 require_quiet(certRef
, out
);
1145 require_quiet(_FilterWithDate(q
->q_match_valid_on_date
, certRef
), out
);
1148 if (q
->q_match_trusted_only
&& (q
->q_class
== &identity_class
|| q
->q_class
== &cert_class
)) {
1150 certRef
= CopyCertificateFromItem(q
, item
);
1151 require_quiet(certRef
, out
);
1152 require_quiet(_FilterWithTrust(CFBooleanGetValue(q
->q_match_trusted_only
), certRef
), out
);
1155 /* Add future match checks here. */
1158 CFReleaseSafe(certRef
);
1162 /****************************************************************************
1163 **************** Beginning of Externally Callable Interface ****************
1164 ****************************************************************************/
1166 void (*SecTaskDiagnoseEntitlements
)(CFArrayRef accessGroups
) = NULL
;
1168 static bool SecEntitlementError(OSStatus status
, CFErrorRef
*error
)
1171 #define SEC_ENTITLEMENT_WARNING CFSTR("com.apple.application-identifier, com.apple.security.application-groups nor keychain-access-groups")
1173 #define SEC_ENTITLEMENT_WARNING CFSTR("application-identifier nor keychain-access-groups")
1176 return SecError(errSecMissingEntitlement
, error
, CFSTR("Client has neither %@ entitlements"), SEC_ENTITLEMENT_WARNING
);
1179 /* AUDIT[securityd](done):
1180 query (ok) is a caller provided dictionary, only its cf type has been checked.
1183 SecItemServerCopyMatching(CFDictionaryRef query
, CFTypeRef
*result
,
1184 SecurityClient
*client
, CFErrorRef
*error
)
1186 CFArrayRef accessGroups
= client
->accessGroups
;
1189 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
))) {
1190 if (SecTaskDiagnoseEntitlements
)
1191 SecTaskDiagnoseEntitlements(accessGroups
);
1192 return SecEntitlementError(errSecMissingEntitlement
, error
);
1195 if (CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), CFSTR("*"))) {
1196 /* Having the special accessGroup "*" allows access to all accessGroups. */
1197 accessGroups
= NULL
;
1201 Query
*q
= query_create_with_limit(query
, client
->musr
, 1, error
);
1203 CFStringRef agrp
= CFDictionaryGetValue(q
->q_item
, kSecAttrAccessGroup
);
1204 if (agrp
&& accessGroupsAllows(accessGroups
, agrp
)) {
1205 // TODO: Return an error if agrp is not NULL and accessGroupsAllows() fails above.
1206 const void *val
= agrp
;
1207 accessGroups
= CFArrayCreate(0, &val
, 1, &kCFTypeArrayCallBacks
);
1209 CFRetainSafe(accessGroups
);
1212 #if TARGET_OS_IPHONE
1213 if (q
->q_sync_bubble
&& client
->inMultiUser
) {
1214 CFReleaseNull(q
->q_musrView
);
1215 q
->q_musrView
= SecMUSRCreateSyncBubbleUserUUID(q
->q_sync_bubble
);
1216 } else if (client
->inMultiUser
&& client
->isNetworkExtension
) {
1217 CFReleaseNull(q
->q_musrView
);
1218 q
->q_musrView
= SecMUSRCreateBothUserAndSystemUUID(client
->uid
);
1219 } else if (q
->q_system_keychain
&& client
->inMultiUser
) {
1220 CFReleaseNull(q
->q_musrView
);
1221 q
->q_musrView
= SecMUSRCopySystemKeychainUUID();
1223 q
->q_system_keychain
= false;
1227 query_set_caller_access_groups(q
, accessGroups
);
1229 /* Sanity check the query. */
1230 if (q
->q_system_keychain
&& !client
->allowSystemKeychain
) {
1231 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for system keychain"));
1232 } else if (q
->q_sync_bubble
&& !client
->allowSyncBubbleKeychain
) {
1233 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for syncbubble keychain"));
1234 } else if (q
->q_system_keychain
&& q
->q_sync_bubble
) {
1235 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("can't do both system and syncbubble keychain"));
1236 } else if (q
->q_use_item_list
) {
1237 ok
= SecError(errSecUseItemListUnsupported
, error
, CFSTR("use item list unsupported"));
1238 } else if (q
->q_match_issuer
&& ((q
->q_class
!= &cert_class
) &&
1239 (q
->q_class
!= &identity_class
))) {
1240 ok
= SecError(errSecUnsupportedOperation
, error
, CFSTR("unsupported match attribute"));
1241 } else if (q
->q_match_policy
&& ((q
->q_class
!= &cert_class
) &&
1242 (q
->q_class
!= &identity_class
))) {
1243 ok
= SecError(errSecUnsupportedOperation
, error
, CFSTR("unsupported kSecMatchPolicy attribute"));
1244 } else if (q
->q_return_type
!= 0 && result
== NULL
) {
1245 ok
= SecError(errSecReturnMissingPointer
, error
, CFSTR("missing pointer"));
1246 } else if (!q
->q_error
) {
1247 ok
= kc_with_dbt(false, error
, ^(SecDbConnectionRef dbt
) {
1248 return s3dl_copy_matching(dbt
, q
, result
, accessGroups
, error
);
1252 CFReleaseSafe(accessGroups
);
1253 if (!query_destroy(q
, error
))
1261 _SecItemCopyMatching(CFDictionaryRef query
, SecurityClient
*client
, CFTypeRef
*result
, CFErrorRef
*error
) {
1262 return SecItemServerCopyMatching(query
, result
, client
, error
);
1265 #if TARGET_OS_IPHONE
1267 SecItemSynchronizable(CFDictionaryRef query
)
1269 bool result
= false;
1270 CFTypeRef value
= CFDictionaryGetValue(query
, kSecAttrSynchronizable
);
1271 if (isBoolean(value
))
1272 return CFBooleanGetValue(value
);
1273 else if (isNumber(value
)) {
1275 (void)CFNumberGetValue(value
, kCFNumberSInt32Type
, &number
);
1284 /* AUDIT[securityd](done):
1285 attributes (ok) is a caller provided dictionary, only its cf type has
1289 _SecItemAdd(CFDictionaryRef attributes
, SecurityClient
*client
, CFTypeRef
*result
, CFErrorRef
*error
)
1291 CFArrayRef accessGroups
= client
->accessGroups
;
1295 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
)) ||
1296 (ag_count
== 1 && CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), kSecAttrAccessGroupToken
))) {
1297 if (SecTaskDiagnoseEntitlements
)
1298 SecTaskDiagnoseEntitlements(accessGroups
);
1299 return SecEntitlementError(errSecMissingEntitlement
, error
);
1302 Query
*q
= query_create_with_limit(attributes
, client
->musr
, 0, error
);
1304 /* Access group sanity checking. */
1305 CFStringRef agrp
= (CFStringRef
)CFDictionaryGetValue(attributes
,
1306 kSecAttrAccessGroup
);
1308 /* Having the special accessGroup "*" allows access to all accessGroups. */
1309 if (CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), CFSTR("*")))
1310 accessGroups
= NULL
;
1313 /* The user specified an explicit access group, validate it. */
1314 if (!accessGroupsAllows(accessGroups
, agrp
))
1315 ok
= SecError(errSecMissingEntitlement
, error
,
1316 CFSTR("explicit accessGroup %@ not in client access %@"), agrp
, accessGroups
);
1318 agrp
= (CFStringRef
)CFArrayGetValueAtIndex(client
->accessGroups
, 0);
1320 /* We are using an implicit access group, add it as if the user
1321 specified it as an attribute. */
1322 query_add_attribute(kSecAttrAccessGroup
, agrp
, q
);
1324 #if TARGET_OS_IPHONE
1325 if (q
->q_system_keychain
&& client
->inMultiUser
) {
1326 CFReleaseNull(q
->q_musrView
);
1327 q
->q_musrView
= SecMUSRCopySystemKeychainUUID();
1329 q
->q_system_keychain
= false;
1331 query_add_attribute_with_desc(&v8musr
, q
->q_musrView
, q
);
1335 query_ensure_access_control(q
, agrp
);
1337 if (q
->q_system_keychain
&& !client
->allowSystemKeychain
) {
1338 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for system keychain"));
1339 } else if (q
->q_sync_bubble
&& !client
->allowSyncBubbleKeychain
) {
1340 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for syncbubble keychain"));
1341 #if TARGET_OS_IPHONE
1342 } else if (q
->q_system_keychain
&& SecItemSynchronizable(attributes
) && !client
->inMultiUser
) {
1343 ok
= SecError(errSecInvalidKey
, error
, CFSTR("Can't store system keychain and synchronizable"));
1345 } else if (q
->q_row_id
) {
1346 ok
= SecError(errSecValuePersistentRefUnsupported
, error
, CFSTR("q_row_id")); // TODO: better error string
1347 } else if (!q
->q_error
) {
1348 ok
= kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
){
1349 return kc_transaction(dbt
, error
, ^{
1350 query_pre_add(q
, true);
1351 return s3dl_query_add(dbt
, q
, result
, error
);
1356 ok
= query_notify_and_destroy(q
, ok
, error
);
1363 /* AUDIT[securityd](done):
1364 query (ok) and attributesToUpdate (ok) are a caller provided dictionaries,
1365 only their cf types have been checked.
1368 _SecItemUpdate(CFDictionaryRef query
, CFDictionaryRef attributesToUpdate
,
1369 SecurityClient
*client
, CFErrorRef
*error
)
1371 CFArrayRef accessGroups
= client
->accessGroups
;
1374 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
)) ||
1375 (ag_count
== 1 && CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), kSecAttrAccessGroupToken
))) {
1376 if (SecTaskDiagnoseEntitlements
)
1377 SecTaskDiagnoseEntitlements(accessGroups
);
1378 return SecEntitlementError(errSecMissingEntitlement
, error
);
1381 if (CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), CFSTR("*"))) {
1382 /* Having the special accessGroup "*" allows access to all accessGroups. */
1383 accessGroups
= NULL
;
1387 Query
*q
= query_create_with_limit(query
, client
->musr
, kSecMatchUnlimited
, error
);
1392 #if TARGET_OS_IPHONE
1393 if (q
->q_system_keychain
&& client
->inMultiUser
) {
1394 CFReleaseNull(q
->q_musrView
);
1395 q
->q_musrView
= SecMUSRCopySystemKeychainUUID();
1397 q
->q_system_keychain
= false;
1401 /* Sanity check the query. */
1402 query_set_caller_access_groups(q
, accessGroups
);
1403 if (q
->q_system_keychain
&& !client
->allowSystemKeychain
) {
1404 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for system keychain"));
1405 } else if (q
->q_sync_bubble
&& !client
->allowSyncBubbleKeychain
) {
1406 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for syncbubble keychain"));
1407 #if TARGET_OS_IPHONE
1408 } else if (q
->q_system_keychain
&& SecItemSynchronizable(attributesToUpdate
) && !client
->inMultiUser
) {
1409 ok
= SecError(errSecInvalidKey
, error
, CFSTR("Can't update an system keychain item with synchronizable"));
1411 } else if (q
->q_use_item_list
) {
1412 ok
= SecError(errSecUseItemListUnsupported
, error
, CFSTR("use item list not supported"));
1413 } else if (q
->q_return_type
& kSecReturnDataMask
) {
1414 /* Update doesn't return anything so don't ask for it. */
1415 ok
= SecError(errSecReturnDataUnsupported
, error
, CFSTR("return data not supported by update"));
1416 } else if (q
->q_return_type
& kSecReturnAttributesMask
) {
1417 ok
= SecError(errSecReturnAttributesUnsupported
, error
, CFSTR("return attributes not supported by update"));
1418 } else if (q
->q_return_type
& kSecReturnRefMask
) {
1419 ok
= SecError(errSecReturnRefUnsupported
, error
, CFSTR("return ref not supported by update"));
1420 } else if (q
->q_return_type
& kSecReturnPersistentRefMask
) {
1421 ok
= SecError(errSecReturnPersistentRefUnsupported
, error
, CFSTR("return persistent ref not supported by update"));
1423 /* Access group sanity checking. */
1424 CFStringRef agrp
= (CFStringRef
)CFDictionaryGetValue(attributesToUpdate
,
1425 kSecAttrAccessGroup
);
1427 /* The user is attempting to modify the access group column,
1428 validate it to make sure the new value is allowable. */
1429 if (!accessGroupsAllows(accessGroups
, agrp
)) {
1430 ok
= SecError(errSecNoAccessForItem
, error
, CFSTR("accessGroup %@ not in %@"), agrp
, accessGroups
);
1436 ok
= kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
1437 return kc_transaction(dbt
, error
, ^{
1438 return s3dl_query_update(dbt
, q
, attributesToUpdate
, accessGroups
, error
);
1443 ok
= query_notify_and_destroy(q
, ok
, error
);
1449 /* AUDIT[securityd](done):
1450 query (ok) is a caller provided dictionary, only its cf type has been checked.
1453 _SecItemDelete(CFDictionaryRef query
, SecurityClient
*client
, CFErrorRef
*error
)
1455 CFArrayRef accessGroups
= client
->accessGroups
;
1458 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
)) ||
1459 (ag_count
== 1 && CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), kSecAttrAccessGroupToken
))) {
1460 if (SecTaskDiagnoseEntitlements
)
1461 SecTaskDiagnoseEntitlements(accessGroups
);
1462 return SecEntitlementError(errSecMissingEntitlement
, error
);
1465 if (CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), CFSTR("*"))) {
1466 /* Having the special accessGroup "*" allows access to all accessGroups. */
1467 accessGroups
= NULL
;
1470 Query
*q
= query_create_with_limit(query
, client
->musr
, kSecMatchUnlimited
, error
);
1473 #if TARGET_OS_IPHONE
1474 if (q
->q_system_keychain
&& client
->inMultiUser
) {
1475 CFReleaseNull(q
->q_musrView
);
1476 q
->q_musrView
= SecMUSRCopySystemKeychainUUID();
1478 q
->q_system_keychain
= false;
1482 query_set_caller_access_groups(q
, accessGroups
);
1483 /* Sanity check the query. */
1484 if (q
->q_system_keychain
&& !client
->allowSystemKeychain
) {
1485 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for system keychain"));
1486 } else if (q
->q_sync_bubble
&& !client
->allowSyncBubbleKeychain
) {
1487 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for syncbubble keychain"));
1488 } else if (q
->q_limit
!= kSecMatchUnlimited
) {
1489 ok
= SecError(errSecMatchLimitUnsupported
, error
, CFSTR("match limit not supported by delete"));
1490 } else if (query_match_count(q
) != 0) {
1491 ok
= SecError(errSecItemMatchUnsupported
, error
, CFSTR("match not supported by delete"));
1492 } else if (q
->q_ref
) {
1493 ok
= SecError(errSecValueRefUnsupported
, error
, CFSTR("value ref not supported by delete"));
1494 } else if (q
->q_row_id
&& query_attr_count(q
)) {
1495 ok
= SecError(errSecItemIllegalQuery
, error
, CFSTR("rowid and other attributes are mutually exclusive"));
1497 ok
= kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
1498 return kc_transaction(dbt
, error
, ^{
1499 return s3dl_query_delete(dbt
, q
, accessGroups
, error
);
1503 ok
= query_notify_and_destroy(q
, ok
, error
);
1510 static bool SecItemDeleteTokenItems(SecDbConnectionRef dbt
, CFTypeRef classToDelete
, CFTypeRef tokenID
, CFArrayRef accessGroups
, SecurityClient
*client
, CFErrorRef
*error
) {
1511 CFTypeRef keys
[] = { kSecClass
, kSecAttrTokenID
};
1512 CFTypeRef values
[] = { classToDelete
, tokenID
};
1514 CFDictionaryRef query
= CFDictionaryCreate(kCFAllocatorDefault
, keys
, values
, 2, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1515 Query
*q
= query_create_with_limit(query
, client
->musr
, kSecMatchUnlimited
, error
);
1519 query_set_caller_access_groups(q
, accessGroups
);
1520 ok
= s3dl_query_delete(dbt
, q
, accessGroups
, error
);
1521 ok
= query_notify_and_destroy(q
, ok
, error
);
1529 static bool SecItemAddTokenItem(SecDbConnectionRef dbt
, CFDictionaryRef attributes
, CFArrayRef accessGroups
, SecurityClient
*client
, CFErrorRef
*error
) {
1531 Query
*q
= query_create_with_limit(attributes
, client
->musr
, 0, error
);
1533 CFStringRef agrp
= kSecAttrAccessGroupToken
;
1534 query_add_attribute(kSecAttrAccessGroup
, agrp
, q
);
1537 query_ensure_access_control(q
, agrp
);
1538 if (q
->q_system_keychain
&& !client
->allowSystemKeychain
) {
1539 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for system keychain"));
1540 } else if (q
->q_sync_bubble
&& !client
->allowSyncBubbleKeychain
) {
1541 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for syncbubble keychain"));
1542 } else if (q
->q_row_id
) {
1543 ok
= SecError(errSecValuePersistentRefUnsupported
, error
, CFSTR("q_row_id")); // TODO: better error string
1544 } else if (!q
->q_error
) {
1545 query_pre_add(q
, true);
1546 ok
= s3dl_query_add(dbt
, q
, NULL
, error
);
1549 ok
= query_notify_and_destroy(q
, ok
, error
);
1556 bool _SecItemUpdateTokenItems(CFStringRef tokenID
, CFArrayRef items
, SecurityClient
*client
, CFErrorRef
*error
) {
1558 CFArrayRef accessGroups
= client
->accessGroups
;
1560 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
))) {
1561 if (SecTaskDiagnoseEntitlements
)
1562 SecTaskDiagnoseEntitlements(accessGroups
);
1563 return SecEntitlementError(errSecMissingEntitlement
, error
);
1566 ok
= kc_with_dbt(true, error
, ^bool (SecDbConnectionRef dbt
) {
1567 return kc_transaction(dbt
, error
, ^bool {
1569 const CFTypeRef classToDelete
[] = { kSecClassGenericPassword
, kSecClassInternetPassword
, kSecClassCertificate
, kSecClassKey
};
1570 for (size_t i
= 0; i
< sizeof(classToDelete
) / sizeof(classToDelete
[0]); ++i
) {
1571 SecItemDeleteTokenItems(dbt
, classToDelete
[i
], tokenID
, accessGroups
, client
, NULL
);
1574 for (CFIndex i
= 0; i
< CFArrayGetCount(items
); ++i
) {
1575 if (!SecItemAddTokenItem(dbt
, CFArrayGetValueAtIndex(items
, i
), accessGroups
, client
, error
))
1581 const CFTypeRef classToDelete
[] = { kSecClassGenericPassword
, kSecClassInternetPassword
, kSecClassCertificate
, kSecClassKey
};
1582 bool deleted
= true;
1583 for (size_t i
= 0; i
< sizeof(classToDelete
) / sizeof(classToDelete
[0]); ++i
) {
1584 if (!SecItemDeleteTokenItems(dbt
, classToDelete
[i
], tokenID
, accessGroups
, client
, error
) && error
&& CFErrorGetCode(*error
) != errSecItemNotFound
) {
1588 else if (error
&& *error
) {
1589 CFReleaseNull(*error
);
1600 /* AUDIT[securityd](done):
1601 No caller provided inputs.
1604 SecItemServerDeleteAll(CFErrorRef
*error
) {
1605 return kc_with_dbt(true, error
, ^bool (SecDbConnectionRef dbt
) {
1606 return (kc_transaction(dbt
, error
, ^bool {
1607 return (SecDbExec(dbt
, CFSTR("DELETE from genp;"), error
) &&
1608 SecDbExec(dbt
, CFSTR("DELETE from inet;"), error
) &&
1609 SecDbExec(dbt
, CFSTR("DELETE from cert;"), error
) &&
1610 SecDbExec(dbt
, CFSTR("DELETE from keys;"), error
));
1611 }) && SecDbExec(dbt
, CFSTR("VACUUM;"), error
));
1616 _SecItemDeleteAll(CFErrorRef
*error
) {
1617 return SecItemServerDeleteAll(error
);
1621 _SecItemServerDeleteAllWithAccessGroups(CFArrayRef accessGroups
, SecurityClient
*client
, CFErrorRef
*error
)
1623 __block
bool ok
= true;
1624 static dispatch_once_t onceToken
;
1625 static CFSetRef illegalAccessGroups
= NULL
;
1627 dispatch_once(&onceToken
, ^{
1628 const CFStringRef values
[] = {
1631 CFSTR("com.apple.security.sos"),
1632 CFSTR("lockdown-identities"),
1634 illegalAccessGroups
= CFSetCreate(NULL
, (const void **)values
, sizeof(values
)/sizeof(values
[0]), &kCFTypeSetCallBacks
);
1637 static const CFTypeRef qclasses
[] = {
1644 require_action_quiet(isArray(accessGroups
), fail
,
1646 SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType
, sSecXPCErrorDomain
, NULL
, error
, NULL
, CFSTR("accessGroups not CFArray, got %@"), accessGroups
));
1648 // TODO: whitelist instead? look for dev IDs like 7123498YQX.com.somedev.app
1650 require_action(CFArrayGetCount(accessGroups
) != 0, fail
,
1652 SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType
, sSecXPCErrorDomain
, NULL
, error
, NULL
, CFSTR("accessGroups e empty")));
1655 // Pre-check accessGroups for prohibited values
1656 CFArrayForEach(accessGroups
, ^(const void *value
) {
1657 CFStringRef agrp
= (CFStringRef
)value
;
1659 if (!isString(agrp
)) {
1660 SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType
, sSecXPCErrorDomain
, NULL
, error
, NULL
,
1661 CFSTR("access not a string: %@"), agrp
);
1663 } else if (CFSetContainsValue(illegalAccessGroups
, agrp
)) {
1664 SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType
, sSecXPCErrorDomain
, NULL
, error
, NULL
,
1665 CFSTR("illegal access group: %@"), accessGroups
);
1671 ok
= kc_with_dbt(true, error
, ^bool(SecDbConnectionRef dbt
) {
1672 return kc_transaction(dbt
, error
, ^bool {
1673 CFErrorRef localError
= NULL
;
1677 for (n
= 0; n
< sizeof(qclasses
)/sizeof(qclasses
[0]) && ok1
; n
++) {
1680 q
= query_create(qclasses
[n
], client
->musr
, NULL
, error
);
1683 (void)s3dl_query_delete(dbt
, q
, accessGroups
, &localError
);
1685 query_destroy(q
, error
);
1686 CFReleaseNull(localError
);
1689 }) && SecDbExec(dbt
, CFSTR("VACUUM"), error
);
1698 // MARK: Shared web credentials
1703 #define SEC_CONST_DECL(k,v) const CFStringRef k = CFSTR(v);
1705 SEC_CONST_DECL (kSecSafariAccessGroup
, "com.apple.cfnetwork");
1706 SEC_CONST_DECL (kSecSafariDefaultComment
, "default");
1707 SEC_CONST_DECL (kSecSafariPasswordsNotSaved
, "Passwords not saved");
1708 SEC_CONST_DECL (kSecSharedCredentialUrlScheme
, "https://");
1709 SEC_CONST_DECL (kSecSharedWebCredentialsService
, "webcredentials");
1711 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1712 static dispatch_once_t sSecSWCInitializeOnce
= 0;
1713 static void * sSecSWCLibrary
= NULL
;
1714 static SWCCheckService_f sSWCCheckService_f
= NULL
;
1715 static SWCSetServiceFlags_f sSWCSetServiceFlags_f
= NULL
;
1717 static OSStatus
_SecSWCEnsuredInitialized(void);
1719 static OSStatus
_SecSWCEnsuredInitialized(void)
1721 __block OSStatus status
= errSecNotAvailable
;
1723 dispatch_once(&sSecSWCInitializeOnce
, ^{
1724 sSecSWCLibrary
= dlopen("/System/Library/PrivateFrameworks/SharedWebCredentials.framework/SharedWebCredentials", RTLD_LAZY
| RTLD_LOCAL
);
1725 assert(sSecSWCLibrary
);
1726 if (sSecSWCLibrary
) {
1727 sSWCCheckService_f
= (SWCCheckService_f
)(uintptr_t) dlsym(sSecSWCLibrary
, "SWCCheckService");
1728 sSWCSetServiceFlags_f
= (SWCSetServiceFlags_f
)(uintptr_t) dlsym(sSecSWCLibrary
, "SWCSetServiceFlags");
1732 if (sSWCCheckService_f
&& sSWCSetServiceFlags_f
) {
1739 #if !TARGET_IPHONE_SIMULATOR
1741 _SecAppDomainApprovalStatus(CFStringRef appID
, CFStringRef fqdn
, CFErrorRef
*error
)
1743 __block SWCFlags flags
= kSWCFlags_None
;
1745 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1746 OSStatus status
= _SecSWCEnsuredInitialized();
1748 SecError(status
, error
, CFSTR("SWC initialize failed"));
1751 CFRetainSafe(appID
);
1753 dispatch_semaphore_t semaphore
= dispatch_semaphore_create(0);
1754 dispatch_retain(semaphore
);
1755 if (0 == sSWCCheckService_f(kSecSharedWebCredentialsService
, appID
, fqdn
,
1756 ^void (OSStatus inStatus
, SWCFlags inFlags
, CFDictionaryRef inDetails
) {
1757 if (!inStatus
) { flags
= inFlags
; }
1758 CFReleaseSafe(appID
);
1759 CFReleaseSafe(fqdn
);
1760 dispatch_semaphore_signal(semaphore
);
1761 dispatch_release(semaphore
);
1762 //secerror("SWCCheckService: inStatus=%d, flags=%0X", inStatus, flags);
1765 // wait for the block to complete, as we need its answer
1766 dispatch_semaphore_wait(semaphore
, DISPATCH_TIME_FOREVER
);
1768 else // didn't queue the block
1770 CFReleaseSafe(appID
);
1771 CFReleaseSafe(fqdn
);
1772 dispatch_release(semaphore
);
1774 dispatch_release(semaphore
);
1776 flags
|= (kSWCFlag_SiteApproved
);
1779 if (!error
) { return flags
; }
1782 // check website approval status
1783 if (!(flags
& kSWCFlag_SiteApproved
)) {
1784 if (flags
& kSWCFlag_Pending
) {
1785 SecError(errSecAuthFailed
, error
, CFSTR("Approval is pending for \"%@\", try later"), fqdn
);
1787 SecError(errSecAuthFailed
, error
, CFSTR("\"%@\" failed to approve \"%@\""), fqdn
, appID
);
1792 // check user approval status
1793 if (flags
& kSWCFlag_UserDenied
) {
1794 SecError(errSecAuthFailed
, error
, CFSTR("User denied access to \"%@\" by \"%@\""), fqdn
, appID
);
1800 #if !TARGET_IPHONE_SIMULATOR
1802 _SecEntitlementContainsDomainForService(CFArrayRef domains
, CFStringRef domain
, CFStringRef service
)
1804 bool result
= false;
1805 CFIndex idx
, count
= (domains
) ? CFArrayGetCount(domains
) : (CFIndex
) 0;
1806 if (!count
|| !domain
|| !service
) {
1809 for (idx
=0; idx
< count
; idx
++) {
1810 CFStringRef str
= (CFStringRef
) CFArrayGetValueAtIndex(domains
, idx
);
1811 if (str
&& CFStringHasPrefix(str
, kSecSharedWebCredentialsService
)) {
1812 CFIndex prefix_len
= CFStringGetLength(kSecSharedWebCredentialsService
)+1;
1813 CFIndex substr_len
= CFStringGetLength(str
) - prefix_len
;
1814 CFRange range
= { prefix_len
, substr_len
};
1815 CFStringRef substr
= CFStringCreateWithSubstring(kCFAllocatorDefault
, str
, range
);
1816 if (substr
&& CFEqual(substr
, domain
)) {
1819 CFReleaseSafe(substr
);
1830 _SecAddNegativeWebCredential(SecurityClient
*client
, CFStringRef fqdn
, CFStringRef appID
, bool forSafari
)
1832 bool result
= false;
1833 if (!fqdn
) { return result
; }
1835 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1836 OSStatus status
= _SecSWCEnsuredInitialized();
1837 if (status
) { return false; }
1839 // update our database
1840 CFRetainSafe(appID
);
1842 if (0 == sSWCSetServiceFlags_f(kSecSharedWebCredentialsService
,
1843 appID
, fqdn
, kSWCFlag_ExternalMask
, kSWCFlag_UserDenied
,
1844 ^void(OSStatus inStatus
, SWCFlags inNewFlags
){
1845 CFReleaseSafe(appID
);
1846 CFReleaseSafe(fqdn
);
1851 else // didn't queue the block
1853 CFReleaseSafe(appID
);
1854 CFReleaseSafe(fqdn
);
1857 if (!forSafari
) { return result
; }
1859 // below this point: create a negative Safari web credential item
1861 CFMutableDictionaryRef attrs
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1862 if (!attrs
) { return result
; }
1864 CFErrorRef error
= NULL
;
1865 CFStringRef accessGroup
= CFSTR("*");
1866 SecurityClient swcclient
= {
1868 .accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&accessGroup
, 1, &kCFTypeArrayCallBacks
),
1869 .allowSystemKeychain
= false,
1870 .allowSyncBubbleKeychain
= false,
1871 .isNetworkExtension
= false,
1872 .musr
= client
->musr
,
1875 CFDictionaryAddValue(attrs
, kSecClass
, kSecClassInternetPassword
);
1876 CFDictionaryAddValue(attrs
, kSecAttrAccessGroup
, kSecSafariAccessGroup
);
1877 CFDictionaryAddValue(attrs
, kSecAttrAuthenticationType
, kSecAttrAuthenticationTypeHTMLForm
);
1878 CFDictionaryAddValue(attrs
, kSecAttrProtocol
, kSecAttrProtocolHTTPS
);
1879 CFDictionaryAddValue(attrs
, kSecAttrServer
, fqdn
);
1880 CFDictionaryAddValue(attrs
, kSecAttrSynchronizable
, kCFBooleanTrue
);
1882 (void)_SecItemDelete(attrs
, &swcclient
, &error
);
1883 CFReleaseNull(error
);
1885 CFDictionaryAddValue(attrs
, kSecAttrAccount
, kSecSafariPasswordsNotSaved
);
1886 CFDictionaryAddValue(attrs
, kSecAttrComment
, kSecSafariDefaultComment
);
1888 CFStringRef label
= CFStringCreateWithFormat(kCFAllocatorDefault
,
1889 NULL
, CFSTR("%@ (%@)"), fqdn
, kSecSafariPasswordsNotSaved
);
1891 CFDictionaryAddValue(attrs
, kSecAttrLabel
, label
);
1892 CFReleaseSafe(label
);
1896 CFDataRef data
= CFDataCreate(kCFAllocatorDefault
, &space
, 1);
1898 CFDictionarySetValue(attrs
, kSecValueData
, data
);
1899 CFReleaseSafe(data
);
1902 CFTypeRef addResult
= NULL
;
1903 result
= _SecItemAdd(attrs
, &swcclient
, &addResult
, &error
);
1905 CFReleaseSafe(addResult
);
1906 CFReleaseSafe(error
);
1907 CFReleaseSafe(attrs
);
1908 CFReleaseSafe(swcclient
.accessGroups
);
1913 /* Specialized version of SecItemAdd for shared web credentials */
1915 _SecAddSharedWebCredential(CFDictionaryRef attributes
,
1916 SecurityClient
*client
,
1917 const audit_token_t
*clientAuditToken
,
1924 SecurityClient swcclient
= {};
1926 CFStringRef fqdn
= CFRetainSafe(CFDictionaryGetValue(attributes
, kSecAttrServer
));
1927 CFStringRef account
= CFDictionaryGetValue(attributes
, kSecAttrAccount
);
1928 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH && !TARGET_OS_TV
1929 CFStringRef password
= CFDictionaryGetValue(attributes
, kSecSharedPassword
);
1931 CFStringRef password
= CFDictionaryGetValue(attributes
, CFSTR("spwd"));
1933 CFStringRef accessGroup
= CFSTR("*");
1934 CFMutableDictionaryRef query
= NULL
, attrs
= NULL
;
1938 // check autofill enabled status
1939 if (!swca_autofill_enabled(clientAuditToken
)) {
1940 SecError(errSecBadReq
, error
, CFSTR("Autofill is not enabled in Safari settings"));
1944 // parse fqdn with CFURL here, since it could be specified as domain:port
1946 CFStringRef urlStr
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%@%@"), kSecSharedCredentialUrlScheme
, fqdn
);
1948 CFURLRef url
= CFURLCreateWithString(kCFAllocatorDefault
, urlStr
, nil
);
1950 CFStringRef hostname
= CFURLCopyHostName(url
);
1952 CFReleaseSafe(fqdn
);
1954 port
= CFURLGetPortNumber(url
);
1958 CFReleaseSafe(urlStr
);
1963 SecError(errSecParam
, error
, CFSTR("No account provided"));
1967 SecError(errSecParam
, error
, CFSTR("No domain provided"));
1971 #if TARGET_IPHONE_SIMULATOR
1972 secerror("app/site association entitlements not checked in Simulator");
1974 OSStatus status
= errSecMissingEntitlement
;
1975 // validate that fqdn is part of caller's shared credential domains entitlement
1977 SecError(status
, error
, CFSTR("Missing application-identifier entitlement"));
1980 if (_SecEntitlementContainsDomainForService(domains
, fqdn
, kSecSharedWebCredentialsService
)) {
1981 status
= errSecSuccess
;
1983 if (errSecSuccess
!= status
) {
1984 CFStringRef msg
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
,
1985 CFSTR("%@ not found in %@ entitlement"), fqdn
, kSecEntitlementAssociatedDomains
);
1987 msg
= CFRetain(CFSTR("Requested domain not found in entitlement"));
1989 SecError(status
, error
, CFSTR("%@"), msg
);
1995 #if TARGET_IPHONE_SIMULATOR
1996 secerror("Ignoring app/site approval state in the Simulator.");
1998 // get approval status for this app/domain pair
1999 SWCFlags flags
= _SecAppDomainApprovalStatus(appID
, fqdn
, error
);
2000 if (!(flags
& kSWCFlag_SiteApproved
)) {
2005 // give ourselves access to see matching items for kSecSafariAccessGroup
2006 swcclient
.task
= NULL
;
2007 swcclient
.accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&accessGroup
, 1, &kCFTypeArrayCallBacks
);
2008 swcclient
.allowSystemKeychain
= false;
2009 swcclient
.musr
= client
->musr
;
2010 swcclient
.allowSystemKeychain
= false;
2011 swcclient
.allowSyncBubbleKeychain
= false;
2012 swcclient
.isNetworkExtension
= false;
2015 // create lookup query
2016 query
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2018 SecError(errSecAllocate
, error
, CFSTR("Unable to create query dictionary"));
2021 CFDictionaryAddValue(query
, kSecClass
, kSecClassInternetPassword
);
2022 CFDictionaryAddValue(query
, kSecAttrAccessGroup
, kSecSafariAccessGroup
);
2023 CFDictionaryAddValue(query
, kSecAttrAuthenticationType
, kSecAttrAuthenticationTypeHTMLForm
);
2024 CFDictionaryAddValue(query
, kSecAttrServer
, fqdn
);
2025 CFDictionaryAddValue(query
, kSecAttrSynchronizable
, kCFBooleanTrue
);
2027 // check for presence of Safari's negative entry ('passwords not saved')
2028 CFDictionarySetValue(query
, kSecAttrAccount
, kSecSafariPasswordsNotSaved
);
2029 ok
= _SecItemCopyMatching(query
, &swcclient
, result
, error
);
2030 if(result
) CFReleaseNull(*result
);
2031 if (error
) CFReleaseNull(*error
);
2033 SecError(errSecDuplicateItem
, error
, CFSTR("Item already exists for this server"));
2037 // now use the provided account (and optional port number, if one was present)
2038 CFDictionarySetValue(query
, kSecAttrAccount
, account
);
2039 if (port
< -1 || port
> 0) {
2040 SInt16 portValueShort
= (port
& 0xFFFF);
2041 CFNumberRef portNumber
= CFNumberCreate(NULL
, kCFNumberSInt16Type
, &portValueShort
);
2042 CFDictionaryAddValue(query
, kSecAttrPort
, portNumber
);
2043 CFReleaseSafe(portNumber
);
2046 // look up existing password
2047 if (_SecItemCopyMatching(query
, &swcclient
, result
, error
)) {
2048 // found it, so this becomes either an "update password" or "delete password" operation
2049 if(result
) CFReleaseNull(*result
);
2050 if(error
) CFReleaseNull(*error
);
2051 bool update
= (password
!= NULL
);
2053 attrs
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2054 CFDataRef credential
= CFStringCreateExternalRepresentation(kCFAllocatorDefault
, password
, kCFStringEncodingUTF8
, 0);
2055 CFDictionaryAddValue(attrs
, kSecValueData
, credential
);
2056 CFReleaseSafe(credential
);
2057 CFDictionaryAddValue(attrs
, kSecAttrComment
, kSecSafariDefaultComment
);
2059 // confirm the update
2060 // (per rdar://16676310 we always prompt, even if there was prior user approval)
2061 ok
= /*approved ||*/ swca_confirm_operation(swca_update_request_id
, clientAuditToken
, query
, error
,
2062 ^void (CFStringRef fqdn
) { _SecAddNegativeWebCredential(client
, fqdn
, appID
, false); });
2064 ok
= _SecItemUpdate(query
, attrs
, &swcclient
, error
);
2068 // confirm the delete
2069 // (per rdar://16676288 we always prompt, even if there was prior user approval)
2070 ok
= /*approved ||*/ swca_confirm_operation(swca_delete_request_id
, clientAuditToken
, query
, error
,
2071 ^void (CFStringRef fqdn
) { _SecAddNegativeWebCredential(client
, fqdn
, appID
, false); });
2073 ok
= _SecItemDelete(query
, &swcclient
, error
);
2077 if (error
) CFReleaseNull(*error
);
2081 if (result
) CFReleaseNull(*result
);
2082 if (error
) CFReleaseNull(*error
);
2084 // password does not exist, so prepare to add it
2086 // a NULL password value removes the existing credential. Since we didn't find it, this is a no-op.
2091 CFStringRef label
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%@ (%@)"), fqdn
, account
);
2093 CFDictionaryAddValue(query
, kSecAttrLabel
, label
);
2094 CFReleaseSafe(label
);
2096 // NOTE: we always expect to use HTTPS for web forms.
2097 CFDictionaryAddValue(query
, kSecAttrProtocol
, kSecAttrProtocolHTTPS
);
2099 CFDataRef credential
= CFStringCreateExternalRepresentation(kCFAllocatorDefault
, password
, kCFStringEncodingUTF8
, 0);
2100 CFDictionarySetValue(query
, kSecValueData
, credential
);
2101 CFReleaseSafe(credential
);
2102 CFDictionarySetValue(query
, kSecAttrComment
, kSecSafariDefaultComment
);
2104 CFReleaseSafe(swcclient
.accessGroups
);
2105 swcclient
.accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&kSecSafariAccessGroup
, 1, &kCFTypeArrayCallBacks
);
2107 // mark the item as created by this function
2108 const int32_t creator_value
= 'swca';
2109 CFNumberRef creator
= CFNumberCreate(kCFAllocatorDefault
, kCFNumberSInt32Type
, &creator_value
);
2111 CFDictionarySetValue(query
, kSecAttrCreator
, creator
);
2112 CFReleaseSafe(creator
);
2117 // (per rdar://16680019, we won't prompt here in the normal case)
2118 ok
= /*approved ||*/ swca_confirm_operation(swca_add_request_id
, clientAuditToken
, query
, error
,
2119 ^void (CFStringRef fqdn
) { _SecAddNegativeWebCredential(client
, fqdn
, appID
, false); });
2123 ok
= _SecItemAdd(query
, &swcclient
, result
, error
);
2127 CFReleaseSafe(attrs
);
2128 CFReleaseSafe(query
);
2129 CFReleaseSafe(swcclient
.accessGroups
);
2130 CFReleaseSafe(fqdn
);
2134 /* Specialized version of SecItemCopyMatching for shared web credentials */
2136 _SecCopySharedWebCredential(CFDictionaryRef query
,
2137 SecurityClient
*client
,
2138 const audit_token_t
*clientAuditToken
,
2144 CFMutableArrayRef credentials
= NULL
;
2145 CFMutableArrayRef foundItems
= NULL
;
2146 CFMutableArrayRef fqdns
= NULL
;
2147 CFStringRef fqdn
= NULL
;
2148 CFStringRef account
= NULL
;
2153 require_quiet(result
, cleanup
);
2154 credentials
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
2155 foundItems
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
2156 fqdns
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
2158 // give ourselves access to see matching items for kSecSafariAccessGroup
2159 CFStringRef accessGroup
= CFSTR("*");
2160 SecurityClient swcclient
= {
2162 .accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&accessGroup
, 1, &kCFTypeArrayCallBacks
),
2163 .allowSystemKeychain
= false,
2164 .allowSyncBubbleKeychain
= false,
2165 .isNetworkExtension
= false,
2166 .musr
= client
->musr
,
2169 // On input, the query dictionary contains optional fqdn and account entries.
2170 fqdn
= CFDictionaryGetValue(query
, kSecAttrServer
);
2171 account
= CFDictionaryGetValue(query
, kSecAttrAccount
);
2173 // Check autofill enabled status
2174 if (!swca_autofill_enabled(clientAuditToken
)) {
2175 SecError(errSecBadReq
, error
, CFSTR("Autofill is not enabled in Safari settings"));
2179 // Check fqdn; if NULL, add domains from caller's entitlement.
2181 CFArrayAppendValue(fqdns
, fqdn
);
2184 CFIndex idx
, count
= CFArrayGetCount(domains
);
2185 for (idx
=0; idx
< count
; idx
++) {
2186 CFStringRef str
= (CFStringRef
) CFArrayGetValueAtIndex(domains
, idx
);
2187 // Parse the entry for our service label prefix
2188 if (str
&& CFStringHasPrefix(str
, kSecSharedWebCredentialsService
)) {
2189 CFIndex prefix_len
= CFStringGetLength(kSecSharedWebCredentialsService
)+1;
2190 CFIndex substr_len
= CFStringGetLength(str
) - prefix_len
;
2191 CFRange range
= { prefix_len
, substr_len
};
2192 fqdn
= CFStringCreateWithSubstring(kCFAllocatorDefault
, str
, range
);
2194 CFArrayAppendValue(fqdns
, fqdn
);
2200 count
= CFArrayGetCount(fqdns
);
2202 SecError(errSecParam
, error
, CFSTR("No domain provided"));
2206 // Aggregate search results for each domain
2207 for (idx
= 0; idx
< count
; idx
++) {
2208 CFMutableArrayRef items
= NULL
;
2209 CFMutableDictionaryRef attrs
= NULL
;
2210 fqdn
= (CFStringRef
) CFArrayGetValueAtIndex(fqdns
, idx
);
2214 // Parse the fqdn for a possible port specifier.
2216 CFStringRef urlStr
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%@%@"), kSecSharedCredentialUrlScheme
, fqdn
);
2218 CFURLRef url
= CFURLCreateWithString(kCFAllocatorDefault
, urlStr
, nil
);
2220 CFStringRef hostname
= CFURLCopyHostName(url
);
2222 CFReleaseSafe(fqdn
);
2224 port
= CFURLGetPortNumber(url
);
2228 CFReleaseSafe(urlStr
);
2232 #if TARGET_IPHONE_SIMULATOR
2233 secerror("app/site association entitlements not checked in Simulator");
2235 OSStatus status
= errSecMissingEntitlement
;
2237 SecError(status
, error
, CFSTR("Missing application-identifier entitlement"));
2238 CFReleaseSafe(fqdn
);
2241 // validate that fqdn is part of caller's entitlement
2242 if (_SecEntitlementContainsDomainForService(domains
, fqdn
, kSecSharedWebCredentialsService
)) {
2243 status
= errSecSuccess
;
2245 if (errSecSuccess
!= status
) {
2246 CFStringRef msg
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
,
2247 CFSTR("%@ not found in %@ entitlement"), fqdn
, kSecEntitlementAssociatedDomains
);
2249 msg
= CFRetain(CFSTR("Requested domain not found in entitlement"));
2251 SecError(status
, error
, CFSTR("%@"), msg
);
2253 CFReleaseSafe(fqdn
);
2258 attrs
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2260 SecError(errSecAllocate
, error
, CFSTR("Unable to create query dictionary"));
2261 CFReleaseSafe(fqdn
);
2264 CFDictionaryAddValue(attrs
, kSecClass
, kSecClassInternetPassword
);
2265 CFDictionaryAddValue(attrs
, kSecAttrAccessGroup
, kSecSafariAccessGroup
);
2266 CFDictionaryAddValue(attrs
, kSecAttrProtocol
, kSecAttrProtocolHTTPS
);
2267 CFDictionaryAddValue(attrs
, kSecAttrAuthenticationType
, kSecAttrAuthenticationTypeHTMLForm
);
2268 CFDictionaryAddValue(attrs
, kSecAttrServer
, fqdn
);
2270 CFDictionaryAddValue(attrs
, kSecAttrAccount
, account
);
2272 if (port
< -1 || port
> 0) {
2273 SInt16 portValueShort
= (port
& 0xFFFF);
2274 CFNumberRef portNumber
= CFNumberCreate(NULL
, kCFNumberSInt16Type
, &portValueShort
);
2275 CFDictionaryAddValue(attrs
, kSecAttrPort
, portNumber
);
2276 CFReleaseSafe(portNumber
);
2278 CFDictionaryAddValue(attrs
, kSecAttrSynchronizable
, kCFBooleanTrue
);
2279 CFDictionaryAddValue(attrs
, kSecMatchLimit
, kSecMatchLimitAll
);
2280 CFDictionaryAddValue(attrs
, kSecReturnAttributes
, kCFBooleanTrue
);
2281 CFDictionaryAddValue(attrs
, kSecReturnData
, kCFBooleanTrue
);
2283 ok
= _SecItemCopyMatching(attrs
, &swcclient
, (CFTypeRef
*)&items
, error
);
2285 // ignore interim error since we have multiple domains to search
2286 CFReleaseNull(*error
);
2288 if (ok
&& items
&& CFGetTypeID(items
) == CFArrayGetTypeID()) {
2289 #if TARGET_IPHONE_SIMULATOR
2290 secerror("Ignoring app/site approval state in the Simulator.");
2291 bool approved
= true;
2293 // get approval status for this app/domain pair
2294 SWCFlags flags
= _SecAppDomainApprovalStatus(appID
, fqdn
, error
);
2296 // ignore interim error since we have multiple domains to check
2297 CFReleaseNull(*error
);
2299 bool approved
= (flags
& kSWCFlag_SiteApproved
);
2302 CFArrayAppendArray(foundItems
, items
, CFRangeMake(0, CFArrayGetCount(items
)));
2305 CFReleaseSafe(items
);
2306 CFReleaseSafe(attrs
);
2307 CFReleaseSafe(fqdn
);
2310 // If matching credentials are found, the credentials provided to the completionHandler
2311 // will be a CFArrayRef containing CFDictionaryRef entries. Each dictionary entry will
2312 // contain the following pairs (see Security/SecItem.h):
2313 // key: kSecAttrServer value: CFStringRef (the website)
2314 // key: kSecAttrAccount value: CFStringRef (the account)
2315 // key: kSecSharedPassword value: CFStringRef (the password)
2317 // key: kSecAttrPort value: CFNumberRef (the port number, if non-standard for https)
2319 count
= CFArrayGetCount(foundItems
);
2320 for (idx
= 0; idx
< count
; idx
++) {
2321 CFDictionaryRef dict
= (CFDictionaryRef
) CFArrayGetValueAtIndex(foundItems
, idx
);
2322 CFMutableDictionaryRef newdict
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2323 if (newdict
&& dict
&& CFGetTypeID(dict
) == CFDictionaryGetTypeID()) {
2324 CFStringRef srvr
= CFDictionaryGetValue(dict
, kSecAttrServer
);
2325 CFStringRef acct
= CFDictionaryGetValue(dict
, kSecAttrAccount
);
2326 CFNumberRef pnum
= CFDictionaryGetValue(dict
, kSecAttrPort
);
2327 CFStringRef icmt
= CFDictionaryGetValue(dict
, kSecAttrComment
);
2328 CFDataRef data
= CFDictionaryGetValue(dict
, kSecValueData
);
2330 CFDictionaryAddValue(newdict
, kSecAttrServer
, srvr
);
2333 CFDictionaryAddValue(newdict
, kSecAttrAccount
, acct
);
2337 if (CFNumberGetValue(pnum
, kCFNumberSInt16Type
, &pval
) &&
2338 (pval
< -1 || pval
> 0)) {
2339 CFDictionaryAddValue(newdict
, kSecAttrPort
, pnum
);
2343 CFStringRef password
= CFStringCreateFromExternalRepresentation(kCFAllocatorDefault
, data
, kCFStringEncodingUTF8
);
2345 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH && !TARGET_OS_TV
2346 CFDictionaryAddValue(newdict
, kSecSharedPassword
, password
);
2348 CFDictionaryAddValue(newdict
, CFSTR("spwd"), password
);
2350 CFReleaseSafe(password
);
2353 if (icmt
&& CFEqual(icmt
, kSecSafariDefaultComment
)) {
2354 CFArrayInsertValueAtIndex(credentials
, 0, newdict
);
2356 CFArrayAppendValue(credentials
, newdict
);
2359 CFReleaseSafe(newdict
);
2366 // create a new array of dictionaries (without the actual password) for picker UI
2367 count
= CFArrayGetCount(credentials
);
2368 CFMutableArrayRef items
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
2369 for (idx
= 0; idx
< count
; idx
++) {
2370 CFDictionaryRef dict
= (CFDictionaryRef
) CFArrayGetValueAtIndex(credentials
, idx
);
2371 CFMutableDictionaryRef newdict
= CFDictionaryCreateMutableCopy(kCFAllocatorDefault
, 0, dict
);
2372 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH && !TARGET_OS_TV
2373 CFDictionaryRemoveValue(newdict
, kSecSharedPassword
);
2375 CFDictionaryRemoveValue(newdict
, CFSTR("spwd"));
2377 CFArrayAppendValue(items
, newdict
);
2378 CFReleaseSafe(newdict
);
2381 // prompt user to select one of the dictionary items
2382 CFDictionaryRef selected
= swca_copy_selected_dictionary(swca_select_request_id
,
2383 clientAuditToken
, items
, error
);
2385 // find the matching item in our credentials array
2386 CFStringRef srvr
= CFDictionaryGetValue(selected
, kSecAttrServer
);
2387 CFStringRef acct
= CFDictionaryGetValue(selected
, kSecAttrAccount
);
2388 CFNumberRef pnum
= CFDictionaryGetValue(selected
, kSecAttrPort
);
2389 for (idx
= 0; idx
< count
; idx
++) {
2390 CFDictionaryRef dict
= (CFDictionaryRef
) CFArrayGetValueAtIndex(credentials
, idx
);
2391 CFStringRef srvr1
= CFDictionaryGetValue(dict
, kSecAttrServer
);
2392 CFStringRef acct1
= CFDictionaryGetValue(dict
, kSecAttrAccount
);
2393 CFNumberRef pnum1
= CFDictionaryGetValue(dict
, kSecAttrPort
);
2395 if (!srvr
|| !srvr1
|| !CFEqual(srvr
, srvr1
)) continue;
2396 if (!acct
|| !acct1
|| !CFEqual(acct
, acct1
)) continue;
2397 if ((pnum
&& pnum1
) && !CFEqual(pnum
, pnum1
)) continue;
2400 CFReleaseSafe(selected
);
2407 CFReleaseSafe(items
);
2408 CFArrayRemoveAllValues(credentials
);
2409 if (selected
&& ok
) {
2410 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
2411 fqdn
= CFDictionaryGetValue(selected
, kSecAttrServer
);
2413 CFArrayAppendValue(credentials
, selected
);
2417 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
2418 // register confirmation with database
2419 OSStatus status
= _SecSWCEnsuredInitialized();
2421 SecError(status
, error
, CFSTR("SWC initialize failed"));
2423 CFReleaseSafe(selected
);
2426 CFRetainSafe(appID
);
2428 if (0 != sSWCSetServiceFlags_f(kSecSharedWebCredentialsService
,
2429 appID
, fqdn
, kSWCFlag_ExternalMask
, kSWCFlag_UserApproved
,
2430 ^void(OSStatus inStatus
, SWCFlags inNewFlags
){
2431 CFReleaseSafe(appID
);
2432 CFReleaseSafe(fqdn
);
2435 // we didn't queue the block
2436 CFReleaseSafe(appID
);
2437 CFReleaseSafe(fqdn
);
2441 CFReleaseSafe(selected
);
2443 else if (NULL
== *error
) {
2444 // found no items, and we haven't already filled in the error
2445 SecError(errSecItemNotFound
, error
, CFSTR("no matching items found"));
2450 CFArrayRemoveAllValues(credentials
);
2451 CFReleaseNull(credentials
);
2453 CFReleaseSafe(foundItems
);
2454 *result
= credentials
;
2455 CFReleaseSafe(swcclient
.accessGroups
);
2456 CFReleaseSafe(fqdns
);
2461 #endif /* TARGET_OS_IOS */
2465 // MARK: Keychain backup
2467 CF_RETURNS_RETAINED CFDataRef
2468 _SecServerKeychainCreateBackup(SecurityClient
*client
, CFDataRef keybag
, CFDataRef passcode
, CFErrorRef
*error
) {
2470 SecDbConnectionRef dbt
= SecDbConnectionAcquire(kc_dbhandle(), false, error
);
2475 if (keybag
== NULL
&& passcode
== NULL
) {
2477 backup
= SecServerExportBackupableKeychain(dbt
, client
, KEYBAG_DEVICE
, backup_keybag_handle
, error
);
2478 #else /* !USE_KEYSTORE */
2480 SecError(errSecParam
, error
, CFSTR("Why are you doing this?"));
2482 #endif /* USE_KEYSTORE */
2484 backup
= SecServerKeychainCreateBackup(dbt
, client
, keybag
, passcode
, error
);
2487 SecDbConnectionRelease(dbt
);
2493 _SecServerKeychainRestore(CFDataRef backup
, SecurityClient
*client
, CFDataRef keybag
, CFDataRef passcode
, CFErrorRef
*error
) {
2494 if (backup
== NULL
|| keybag
== NULL
)
2495 return SecError(errSecParam
, error
, CFSTR("backup or keybag missing"));
2497 __block
bool ok
= true;
2498 ok
&= SecDbPerformWrite(kc_dbhandle(), error
, ^(SecDbConnectionRef dbconn
) {
2499 ok
= SecServerKeychainRestore(dbconn
, client
, backup
, keybag
, passcode
, error
);
2503 SecKeychainChanged();
2510 _SecServerBackupCopyUUID(CFDataRef data
, CFErrorRef
*error
)
2512 CFStringRef uuid
= NULL
;
2513 CFDictionaryRef backup
;
2515 backup
= CFPropertyListCreateWithData(kCFAllocatorDefault
, data
,
2516 kCFPropertyListImmutable
, NULL
,
2518 if (isDictionary(backup
)) {
2519 uuid
= SecServerBackupGetKeybagUUID(backup
);
2523 CFReleaseNull(backup
);
2531 // MARK: SecItemDataSource
2533 // Make sure to call this before any writes to the keychain, so that we fire
2534 // up the engines to monitor manifest changes.
2535 SOSDataSourceFactoryRef
SecItemDataSourceFactoryGetDefault(void) {
2536 return SecItemDataSourceFactoryGetShared(kc_dbhandle());
2539 /* AUDIT[securityd]:
2540 args_in (ok) is a caller provided, CFDictionaryRef.
2543 CF_RETURNS_RETAINED CFArrayRef
2544 _SecServerKeychainSyncUpdateMessage(CFDictionaryRef updates
, CFErrorRef
*error
) {
2545 // This never fails, trust us!
2546 return SOSCCHandleUpdateMessage(updates
);
2550 // Truthiness in the cloud backup/restore support.
2553 static CFDictionaryRef
2554 _SecServerCopyTruthInTheCloud(CFDataRef keybag
, CFDataRef password
,
2555 CFDictionaryRef backup
, CFErrorRef
*error
)
2557 SOSManifestRef mold
= NULL
, mnow
= NULL
, mdelete
= NULL
, madd
= NULL
;
2558 __block CFMutableDictionaryRef backup_new
= NULL
;
2559 keybag_handle_t bag_handle
;
2560 if (!ks_open_keybag(keybag
, password
, &bag_handle
, error
))
2563 // We need to have a datasource singleton for protection domain
2564 // kSecAttrAccessibleWhenUnlocked and keep a single shared engine
2565 // instance around which we create in the datasource constructor as well.
2566 SOSDataSourceFactoryRef dsf
= SecItemDataSourceFactoryGetDefault();
2567 SOSDataSourceRef ds
= SOSDataSourceFactoryCreateDataSource(dsf
, kSecAttrAccessibleWhenUnlocked
, error
);
2569 backup_new
= backup
? CFDictionaryCreateMutableCopy(kCFAllocatorDefault
, 0, backup
) : CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2570 mold
= SOSCreateManifestWithBackup(backup
, error
);
2571 SOSEngineRef engine
= SOSDataSourceGetSharedEngine(ds
, error
);
2572 mnow
= SOSEngineCopyManifest(engine
, NULL
);
2574 mnow
= SOSDataSourceCopyManifestWithViewNameSet(ds
, SOSViewsGetV0ViewSet(), error
);
2577 CFReleaseNull(backup_new
);
2578 secerror("failed to obtain manifest for keychain: %@", error
? *error
: NULL
);
2580 SOSManifestDiff(mold
, mnow
, &mdelete
, &madd
, error
);
2583 // Delete everything from the new_backup that is no longer in the datasource according to the datasources manifest.
2584 SOSManifestForEach(mdelete
, ^(CFDataRef digest_data
, bool *stop
) {
2585 CFStringRef deleted_item_key
= CFDataCopyHexString(digest_data
);
2586 CFDictionaryRemoveValue(backup_new
, deleted_item_key
);
2587 CFRelease(deleted_item_key
);
2590 CFMutableArrayRef changes
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
2591 SOSDataSourceForEachObject(ds
, NULL
, madd
, error
, ^void(CFDataRef digest
, SOSObjectRef object
, bool *stop
) {
2592 CFErrorRef localError
= NULL
;
2593 CFDataRef digest_data
= NULL
;
2594 CFTypeRef value
= NULL
;
2596 // Key in our manifest can't be found in db, remove it from our manifest
2597 SOSChangesAppendDelete(changes
, digest
);
2598 } else if (!(digest_data
= SOSObjectCopyDigest(ds
, object
, &localError
))
2599 || !(value
= SOSObjectCopyBackup(ds
, object
, bag_handle
, &localError
))) {
2600 if (SecErrorGetOSStatus(localError
) == errSecDecode
) {
2601 // Ignore decode errors, pretend the objects aren't there
2602 CFRelease(localError
);
2603 // Object undecodable, remove it from our manifest
2604 SOSChangesAppendDelete(changes
, digest
);
2606 // Stop iterating and propagate out all other errors.
2608 *error
= localError
;
2609 CFReleaseNull(backup_new
);
2612 // TODO: Should we skip tombstones here?
2613 CFStringRef key
= CFDataCopyHexString(digest_data
);
2614 CFDictionarySetValue(backup_new
, key
, value
);
2617 CFReleaseSafe(digest_data
);
2618 CFReleaseSafe(value
);
2619 }) || CFReleaseNull(backup_new
);
2621 if (CFArrayGetCount(changes
)) {
2622 if (!SOSEngineUpdateChanges(engine
, kSOSDataSourceSOSTransaction
, changes
, error
)) {
2623 CFReleaseNull(backup_new
);
2626 CFReleaseSafe(changes
);
2628 SOSDataSourceRelease(ds
, error
) || CFReleaseNull(backup_new
);
2631 CFReleaseSafe(mold
);
2632 CFReleaseSafe(mnow
);
2633 CFReleaseSafe(madd
);
2634 CFReleaseSafe(mdelete
);
2635 ks_close_keybag(bag_handle
, error
) || CFReleaseNull(backup_new
);
2641 _SecServerRestoreTruthInTheCloud(CFDataRef keybag
, CFDataRef password
, CFDictionaryRef backup_in
, CFErrorRef
*error
) {
2642 __block
bool ok
= true;
2643 keybag_handle_t bag_handle
;
2644 if (!ks_open_keybag(keybag
, password
, &bag_handle
, error
))
2647 SOSManifestRef mbackup
= SOSCreateManifestWithBackup(backup_in
, error
);
2649 SOSDataSourceFactoryRef dsf
= SecItemDataSourceFactoryGetDefault();
2650 SOSDataSourceRef ds
= SOSDataSourceFactoryCreateDataSource(dsf
, kSecAttrAccessibleWhenUnlocked
, error
);
2651 ok
&= ds
&& SOSDataSourceWith(ds
, error
, ^(SOSTransactionRef txn
, bool *commit
) {
2652 SOSManifestRef mnow
= SOSDataSourceCopyManifestWithViewNameSet(ds
, SOSViewsGetV0BackupViewSet(), error
);
2653 SOSManifestRef mdelete
= NULL
, madd
= NULL
;
2654 SOSManifestDiff(mnow
, mbackup
, &mdelete
, &madd
, error
);
2656 // Don't delete everything in datasource not in backup.
2658 // Add items from the backup
2659 SOSManifestForEach(madd
, ^void(CFDataRef e
, bool *stop
) {
2660 CFDictionaryRef item
= NULL
;
2661 CFStringRef sha1
= CFDataCopyHexString(e
);
2663 item
= CFDictionaryGetValue(backup_in
, sha1
);
2667 CFErrorRef localError
= NULL
;
2669 if (!SOSObjectRestoreObject(ds
, txn
, bag_handle
, item
, &localError
)) {
2670 OSStatus status
= SecErrorGetOSStatus(localError
);
2671 if (status
== errSecDuplicateItem
) {
2672 // Log and ignore duplicate item errors during restore
2673 secnotice("titc", "restore %@ not replacing existing item", item
);
2674 } else if (status
== errSecDecode
) {
2675 // Log and ignore corrupted item errors during restore
2676 secnotice("titc", "restore %@ skipping corrupted item %@", item
, localError
);
2678 if (status
== errSecInteractionNotAllowed
)
2680 // Propagate the first other error upwards (causing the restore to fail).
2681 secerror("restore %@ failed %@", item
, localError
);
2683 if (error
&& !*error
) {
2684 *error
= localError
;
2688 CFReleaseSafe(localError
);
2692 ok
&= SOSDataSourceRelease(ds
, error
);
2693 CFReleaseNull(mdelete
);
2694 CFReleaseNull(madd
);
2695 CFReleaseNull(mnow
);
2700 ok
&= ks_close_keybag(bag_handle
, error
);
2706 CF_RETURNS_RETAINED CFDictionaryRef
2707 _SecServerBackupSyncable(CFDictionaryRef backup
, CFDataRef keybag
, CFDataRef password
, CFErrorRef
*error
) {
2708 require_action_quiet(isData(keybag
), errOut
, SecError(errSecParam
, error
, CFSTR("keybag %@ not a data"), keybag
));
2709 require_action_quiet(!backup
|| isDictionary(backup
), errOut
, SecError(errSecParam
, error
, CFSTR("backup %@ not a dictionary"), backup
));
2710 require_action_quiet(!password
|| isData(password
), errOut
, SecError(errSecParam
, error
, CFSTR("password %@ not a data"), password
));
2712 return _SecServerCopyTruthInTheCloud(keybag
, password
, backup
, error
);
2719 _SecServerRestoreSyncable(CFDictionaryRef backup
, CFDataRef keybag
, CFDataRef password
, CFErrorRef
*error
) {
2721 require_action_quiet(isData(keybag
), errOut
, ok
= SecError(errSecParam
, error
, CFSTR("keybag %@ not a data"), keybag
));
2722 require_action_quiet(isDictionary(backup
), errOut
, ok
= SecError(errSecParam
, error
, CFSTR("backup %@ not a dictionary"), backup
));
2725 require_action_quiet(isData(password
), errOut
, ok
= SecError(errSecParam
, error
, CFSTR("password not a data")));
2728 ok
= _SecServerRestoreTruthInTheCloud(keybag
, password
, backup
, error
);
2734 bool _SecServerRollKeysGlue(bool force
, CFErrorRef
*error
) {
2735 return _SecServerRollKeys(force
, NULL
, error
);
2739 bool _SecServerRollKeys(bool force
, SecurityClient
*client
, CFErrorRef
*error
) {
2741 uint32_t keystore_generation_status
= 0;
2742 if (aks_generation(KEYBAG_DEVICE
, generation_noop
, &keystore_generation_status
))
2744 uint32_t current_generation
= keystore_generation_status
& generation_current
;
2746 return kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
2747 bool up_to_date
= s3dl_dbt_keys_current(dbt
, current_generation
, NULL
);
2749 if (force
&& !up_to_date
) {
2750 up_to_date
= s3dl_dbt_update_keys(dbt
, client
, error
);
2752 secerror("Completed roll keys.");
2753 up_to_date
= s3dl_dbt_keys_current(dbt
, current_generation
, NULL
);
2756 secerror("Failed to roll keys.");
2768 * Sync bubble migration code
2771 struct SyncBubbleRule
{
2772 CFStringRef attribute
;
2777 TransmogrifyItemsToSyncBubble(SecurityClient
*client
, uid_t uid
,
2780 const SecDbClass
*qclass
,
2781 struct SyncBubbleRule
*items
, CFIndex nItems
,
2784 CFMutableDictionaryRef updateAttributes
= NULL
;
2785 CFDataRef syncBubbleView
= NULL
;
2786 CFDataRef activeUserView
= NULL
;
2791 syncBubbleView
= SecMUSRCreateSyncBubbleUserUUID(uid
);
2792 require(syncBubbleView
, fail
);
2794 activeUserView
= SecMUSRCreateActiveUserUUID(uid
);
2795 require(activeUserView
, fail
);
2798 if ((onlyDelete
&& !copyToo
) || !onlyDelete
) {
2801 * Clean out items first
2804 secnotice("syncbubble", "cleaning out old items");
2806 q
= query_create(qclass
, NULL
, NULL
, error
);
2809 q
->q_limit
= kSecMatchUnlimited
;
2810 q
->q_keybag
= device_keybag_handle
;
2812 for (n
= 0; n
< nItems
; n
++) {
2813 query_add_attribute(items
[n
].attribute
, items
[n
].value
, q
);
2815 q
->q_musrView
= CFRetain(syncBubbleView
);
2816 require(q
->q_musrView
, fail
);
2818 kc_with_dbt(false, error
, ^(SecDbConnectionRef dbt
) {
2819 return kc_transaction(dbt
, error
, ^{
2820 return s3dl_query_delete(dbt
, q
, NULL
, error
);
2824 query_destroy(q
, NULL
);
2829 if (onlyDelete
|| !copyToo
) {
2830 secnotice("syncbubble", "skip migration of items");
2833 * Copy over items from EMCS to sync bubble
2836 secnotice("syncbubble", "migrating sync bubble items");
2838 q
= query_create(qclass
, NULL
, NULL
, error
);
2841 q
->q_return_type
= kSecReturnDataMask
| kSecReturnAttributesMask
;
2842 q
->q_limit
= kSecMatchUnlimited
;
2843 q
->q_keybag
= device_keybag_handle
; /* XXX change to session key bag when it exists */
2845 for (n
= 0; n
< nItems
; n
++) {
2846 query_add_or_attribute(items
[n
].attribute
, items
[n
].value
, q
);
2848 query_add_or_attribute(CFSTR("musr"), activeUserView
, q
);
2849 q
->q_musrView
= CFRetain(activeUserView
);
2851 updateAttributes
= CFDictionaryCreateMutableForCFTypes(NULL
);
2852 require(updateAttributes
, fail
);
2854 CFDictionarySetValue(updateAttributes
, CFSTR("musr"), syncBubbleView
); /* XXX should use kSecAttrMultiUser */
2857 kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
2858 return kc_transaction(dbt
, error
, ^{
2859 CFErrorRef error2
= NULL
;
2861 SecDbItemSelect(q
, dbt
, &error2
, NULL
, ^bool(const SecDbAttr
*attr
) {
2862 return CFDictionaryGetValue(q
->q_item
, attr
->name
);
2863 }, NULL
, NULL
, ^(SecDbItemRef item
, bool *stop
) {
2864 CFErrorRef error3
= NULL
;
2865 secinfo("syncbubble", "migrating item");
2867 SecDbItemRef new_item
= SecDbItemCopyWithUpdates(item
, updateAttributes
, NULL
);
2868 if (new_item
== NULL
)
2871 SecDbItemClearRowId(new_item
, NULL
);
2873 if (!SecDbItemSetKeybag(new_item
, device_keybag_handle
, NULL
)) {
2874 CFRelease(new_item
);
2878 if (!SecDbItemInsert(new_item
, dbt
, &error3
)) {
2879 secnotice("syncbubble", "migration failed with %@ for item %@", error3
, new_item
);
2881 CFRelease(new_item
);
2882 CFReleaseNull(error3
);
2884 CFReleaseNull(error2
);
2893 CFReleaseNull(syncBubbleView
);
2894 CFReleaseNull(activeUserView
);
2895 CFReleaseNull(updateAttributes
);
2897 query_destroy(q
, NULL
);
2902 static struct SyncBubbleRule PCSItems
[] = {
2904 .attribute
= CFSTR("agrp"),
2905 .value
= CFSTR("com.apple.ProtectedCloudStorage"),
2908 static struct SyncBubbleRule NSURLSesssiond
[] = {
2910 .attribute
= CFSTR("agrp"),
2911 .value
= CFSTR("com.apple.nsurlsessiond"),
2914 static struct SyncBubbleRule AccountsdItems
[] = {
2916 .attribute
= CFSTR("svce"),
2917 .value
= CFSTR("com.apple.account.AppleAccount.token"),
2920 .attribute
= CFSTR("svce"),
2921 .value
= CFSTR("com.apple.account.AppleAccount.password"),
2924 .attribute
= CFSTR("svce"),
2925 .value
= CFSTR("com.apple.account.AppleAccount.rpassword"),
2928 .attribute
= CFSTR("svce"),
2929 .value
= CFSTR("com.apple.account.idms.token"),
2932 .attribute
= CFSTR("svce"),
2933 .value
= CFSTR("com.apple.account.idms.continuation-key"),
2936 .attribute
= CFSTR("svce"),
2937 .value
= CFSTR("com.apple.account.CloudKit.token"),
2941 static struct SyncBubbleRule MobileMailItems
[] = {
2943 .attribute
= CFSTR("svce"),
2944 .value
= CFSTR("com.apple.account.IMAP.password"),
2947 .attribute
= CFSTR("svce"),
2948 .value
= CFSTR("com.apple.account.SMTP.password"),
2951 .attribute
= CFSTR("svce"),
2952 .value
= CFSTR("com.apple.account.Exchange.password"),
2955 .attribute
= CFSTR("svce"),
2956 .value
= CFSTR("com.apple.account.Hotmail.password"),
2959 .attribute
= CFSTR("svce"),
2960 .value
= CFSTR("com.apple.account.Google.password"),
2963 .attribute
= CFSTR("svce"),
2964 .value
= CFSTR("com.apple.account.Google.oauth-token"),
2967 .attribute
= CFSTR("svce"),
2968 .value
= CFSTR("com.apple.account.Google.oath-refresh-token"),
2971 .attribute
= CFSTR("svce"),
2972 .value
= CFSTR("com.apple.account.Yahoo.password"),
2975 .attribute
= CFSTR("svce"),
2976 .value
= CFSTR("com.apple.account.Yahoo.oauth-token"),
2979 .attribute
= CFSTR("svce"),
2980 .value
= CFSTR("com.apple.account.Yahoo.oauth-token-nosync"),
2983 .attribute
= CFSTR("svce"),
2984 .value
= CFSTR("com.apple.account.Yahoo.oath-refresh-token"),
2987 .attribute
= CFSTR("svce"),
2988 .value
= CFSTR("com.apple.account.IMAPNotes.password"),
2991 .attribute
= CFSTR("svce"),
2992 .value
= CFSTR("com.apple.account.IMAPMail.password"),
2995 .attribute
= CFSTR("svce"),
2996 .value
= CFSTR("com.apple.account.126.password"),
2999 .attribute
= CFSTR("svce"),
3000 .value
= CFSTR("com.apple.account.163.password"),
3003 .attribute
= CFSTR("svce"),
3004 .value
= CFSTR("com.apple.account.aol.password"),
3009 ArrayContains(CFArrayRef array
, CFStringRef service
)
3011 return CFArrayContainsValue(array
, CFRangeMake(0, CFArrayGetCount(array
)), service
);
3015 _SecServerTransmogrifyToSyncBubble(CFArrayRef services
, uid_t uid
, SecurityClient
*client
, CFErrorRef
*error
)
3017 bool copyCloudAuthToken
= false;
3018 bool copyMobileMail
= false;
3020 bool copyPCS
= false;
3021 bool onlyDelete
= false;
3022 bool copyNSURLSesssion
= false;
3024 if (!client
->inMultiUser
)
3027 secnotice("syncbubble", "migration for uid %d uid for services %@", (int)uid
, services
);
3029 #if TARGET_OS_SIMULATOR
3032 if (uid
!= (uid_t
)client
->activeUser
)
3035 #error "no sync bubble on other platforms"
3039 * First select that services to copy/delete
3042 if (ArrayContains(services
, CFSTR("com.apple.bird.usermanager.sync"))
3043 || ArrayContains(services
, CFSTR("com.apple.cloudphotod.sync"))
3044 || ArrayContains(services
, CFSTR("com.apple.cloudphotod.syncstakeholder"))
3045 || ArrayContains(services
, CFSTR("com.apple.cloudd.usermanager.sync")))
3047 copyCloudAuthToken
= true;
3051 if (ArrayContains(services
, CFSTR("com.apple.nsurlsessiond.usermanager.sync")))
3053 copyCloudAuthToken
= true;
3054 copyNSURLSesssion
= true;
3057 if (ArrayContains(services
, CFSTR("com.apple.syncdefaultsd.usermanager.sync"))) {
3058 copyCloudAuthToken
= true;
3060 if (ArrayContains(services
, CFSTR("com.apple.mailq.sync")) || ArrayContains(services
, CFSTR("com.apple.mailq.sync.xpc"))) {
3061 copyCloudAuthToken
= true;
3062 copyMobileMail
= true;
3067 * The actually copy/delete the items selected
3070 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyPCS
, &inet_class
, PCSItems
, sizeof(PCSItems
)/sizeof(PCSItems
[0]), error
);
3072 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyPCS
, &genp_class
, PCSItems
, sizeof(PCSItems
)/sizeof(PCSItems
[0]), error
);
3076 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyMobileMail
, &genp_class
, MobileMailItems
, sizeof(MobileMailItems
)/sizeof(MobileMailItems
[0]), error
);
3080 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyCloudAuthToken
, &genp_class
, AccountsdItems
, sizeof(AccountsdItems
)/sizeof(AccountsdItems
[0]), error
);
3084 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyNSURLSesssion
, &inet_class
, NSURLSesssiond
, sizeof(NSURLSesssiond
)/sizeof(NSURLSesssiond
[0]), error
);
3092 * Migrate from user keychain to system keychain when switching to edu mode
3096 _SecServerTransmogrifyToSystemKeychain(SecurityClient
*client
, CFErrorRef
*error
)
3098 __block
bool ok
= true;
3101 * we are not in multi user yet, about to switch, otherwise we would
3102 * check that for client->inMultiuser here
3105 kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
3106 return kc_transaction(dbt
, error
, ^{
3107 CFDataRef systemUUID
= SecMUSRGetSystemKeychainUUID();
3109 const SecDbSchema
*newSchema
= kc_schemas
[0];
3110 SecDbClass
const *const *kcClass
;
3112 for (kcClass
= newSchema
->classes
; *kcClass
!= NULL
; kcClass
++) {
3113 CFErrorRef localError
= NULL
;
3116 if (*kcClass
== &tversion_class
|| *kcClass
== &identity_class
)
3119 q
= query_create(*kcClass
, SecMUSRGetSingleUserKeychainUUID(), NULL
, error
);
3123 ok
&= SecDbItemSelect(q
, dbt
, error
, ^bool(const SecDbAttr
*attr
) {
3124 return (attr
->flags
& kSecDbInFlag
) != 0;
3125 }, ^bool(const SecDbAttr
*attr
) {
3126 // No filtering please.
3128 }, ^bool(CFMutableStringRef sql
, bool *needWhere
) {
3129 SecDbAppendWhereOrAnd(sql
, needWhere
);
3130 CFStringAppendFormat(sql
, NULL
, CFSTR("musr = ?"));
3132 }, ^bool(sqlite3_stmt
*stmt
, int col
) {
3133 return SecDbBindObject(stmt
, col
++, SecMUSRGetSingleUserKeychainUUID(), error
);
3134 }, ^(SecDbItemRef item
, bool *stop
) {
3135 CFErrorRef localError
= NULL
;
3137 if (!SecDbItemSetValueWithName(item
, kSecAttrMultiUser
, systemUUID
, &localError
)) {
3138 secerror("item: %@ update musr to system failed: %@", item
, localError
);
3143 if (!SecDbItemDoUpdate(item
, item
, dbt
, &localError
, ^bool (const SecDbAttr
*attr
) {
3144 return attr
->kind
== kSecDbRowIdAttr
;
3146 secerror("item: %@ insert during UPDATE: %@", item
, localError
);
3152 SecErrorPropagate(localError
, error
);
3153 CFReleaseSafe(localError
);
3157 query_destroy(q
, &localError
);
3168 * Delete account from local usage
3172 _SecServerDeleteMUSERViews(SecurityClient
*client
, uid_t uid
, CFErrorRef
*error
)
3174 return kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
3175 CFDataRef musrView
= NULL
, syncBubbleView
= NULL
;
3178 syncBubbleView
= SecMUSRCreateSyncBubbleUserUUID(uid
);
3179 require(syncBubbleView
, fail
);
3181 musrView
= SecMUSRCreateActiveUserUUID(uid
);
3182 require(musrView
, fail
);
3184 require(ok
= SecServerDeleteAllForUser(dbt
, syncBubbleView
, false, error
), fail
);
3185 require(ok
= SecServerDeleteAllForUser(dbt
, musrView
, false, error
), fail
);
3188 CFReleaseNull(syncBubbleView
);
3189 CFReleaseNull(musrView
);
3195 #endif /* TARGET_OS_IOS */
3198 _SecServerGetKeyStats(const SecDbClass
*qclass
,
3199 struct _SecServerKeyStats
*stats
)
3201 __block CFErrorRef error
= NULL
;
3204 Query
*q
= query_create(qclass
, NULL
, NULL
, &error
);
3207 q
->q_return_type
= kSecReturnDataMask
| kSecReturnAttributesMask
;
3208 q
->q_limit
= kSecMatchUnlimited
;
3209 q
->q_keybag
= KEYBAG_DEVICE
;
3210 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleWhenUnlocked
, q
);
3211 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleAfterFirstUnlock
, q
);
3212 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleAlways
, q
);
3213 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleWhenUnlockedThisDeviceOnly
, q
);
3214 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
, q
);
3215 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleAlwaysThisDeviceOnly
, q
);
3216 query_add_attribute(kSecAttrTombstone
, kCFBooleanFalse
, q
);
3218 kc_with_dbt(false, &error
, ^(SecDbConnectionRef dbconn
) {
3219 CFErrorRef error2
= NULL
;
3220 __block CFIndex totalSize
= 0;
3221 stats
->maxDataSize
= 0;
3223 SecDbItemSelect(q
, dbconn
, &error2
, NULL
, ^bool(const SecDbAttr
*attr
) {
3224 return CFDictionaryContainsKey(q
->q_item
, attr
->name
);
3225 }, NULL
, NULL
, ^(SecDbItemRef item
, bool *stop
) {
3226 CFErrorRef error3
= NULL
;
3227 CFDataRef data
= SecDbItemGetValue(item
, &v6v_Data
, &error3
);
3229 CFIndex size
= CFDataGetLength(data
);
3230 if (size
> stats
->maxDataSize
)
3231 stats
->maxDataSize
= size
;
3235 CFReleaseNull(error3
);
3237 CFReleaseNull(error2
);
3239 stats
->averageSize
= totalSize
/ stats
->items
;
3248 CFReleaseNull(error
);
3250 query_destroy(q
, NULL
);