2 * Copyright (c) 2006-2017 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 <utilities/SecDb.h>
37 #include <securityd/SecDbKeychainItem.h>
38 #include <securityd/SOSCloudCircleServer.h>
39 #include <Security/SecBasePriv.h>
40 #include <Security/SecItemPriv.h>
41 #include <Security/SecItemInternal.h>
42 #include <Security/SecureObjectSync/SOSChangeTracker.h>
43 #include <Security/SecureObjectSync/SOSDigestVector.h>
44 #include <Security/SecureObjectSync/SOSEngine.h>
45 #include <Security/SecureObjectSync/SOSViews.h>
46 #include <Security/SecTrustPriv.h>
47 #include <Security/SecTrustInternal.h>
48 #include <Security/SecCertificatePriv.h>
49 #include <Security/SecEntitlements.h>
50 #include <Security/SecSignpost.h>
52 #include <keychain/ckks/CKKS.h>
55 #include <MobileKeyBag/MobileKeyBag.h>
56 #include <Kernel/IOKit/crypto/AppleKeyStoreDefs.h>
58 // TODO: Make this include work on both platforms. rdar://problem/16526848
59 #if TARGET_OS_EMBEDDED
60 #include <Security/SecEntitlements.h>
62 /* defines from <Security/SecEntitlements.h> */
63 #define kSecEntitlementAssociatedDomains CFSTR("com.apple.developer.associated-domains")
64 #define kSecEntitlementPrivateAssociatedDomains CFSTR("com.apple.private.associated-domains")
67 #if TARGET_OS_EMBEDDED
68 #include <utilities/SecADWrapper.h>
73 #include <utilities/array_size.h>
74 #include <utilities/SecFileLocations.h>
75 #include <utilities/SecTrace.h>
76 #include <utilities/SecXPCError.h>
77 #include <utilities/sec_action.h>
78 #include <Security/SecuritydXPC.h>
79 #include "swcagent_client.h"
80 #include "SecPLWrappers.h"
82 #if TARGET_OS_IOS && !TARGET_OS_BRIDGE
83 #include <SharedWebCredentials/SharedWebCredentials.h>
86 /* Changed the name of the keychain changed notification, for testing */
87 static const char *g_keychain_changed_notification
= kSecServerKeychainChangedNotification
;
89 void SecItemServerSetKeychainChangedNotification(const char *notification_name
)
91 g_keychain_changed_notification
= notification_name
;
94 void SecKeychainChanged() {
95 static dispatch_once_t once
;
96 static sec_action_t action
;
98 dispatch_once(&once
, ^{
99 action
= sec_action_create("SecKeychainChanged", 1);
100 sec_action_set_handler(action
, ^{
101 uint32_t result
= notify_post(g_keychain_changed_notification
);
102 if (result
== NOTIFY_STATUS_OK
)
103 secnotice("item", "Sent %s", g_keychain_changed_notification
);
105 secerror("notify_post %s returned: %" PRIu32
, g_keychain_changed_notification
, result
);
109 sec_action_perform(action
);
112 /* Return the current database version in *version. */
113 bool SecKeychainDbGetVersion(SecDbConnectionRef dbt
, int *version
, CFErrorRef
*error
)
115 __block
bool ok
= true;
116 __block CFErrorRef localError
= NULL
;
117 __block
bool found
= false;
120 * First check for the version table itself
123 ok
&= SecDbPrepare(dbt
, CFSTR("SELECT name FROM sqlite_master WHERE type='table' AND name='tversion'"), &localError
, ^(sqlite3_stmt
*stmt
) {
124 ok
= SecDbStep(dbt
, stmt
, NULL
, ^(bool *stop
) {
129 require_action(ok
, out
, SecDbError(SQLITE_CORRUPT
, error
, CFSTR("Failed to read sqlite_master table: %@"), localError
));
131 secnotice("upgr", "no tversion table, will setup a new database: %@", localError
);
137 * Now build up major.minor
140 ok
&= SecDbPrepare(dbt
, CFSTR("SELECT version FROM tversion"), &localError
, ^(sqlite3_stmt
*stmt
) {
141 ok
= SecDbStep(dbt
, stmt
, NULL
, ^(bool *stop
) {
142 *version
= sqlite3_column_int(stmt
, 0);
147 if (ok
&& (*version
& 0xffff) >= 9) {
148 ok
&= SecDbPrepare(dbt
, CFSTR("SELECT minor FROM tversion WHERE version = ?"), &localError
, ^(sqlite3_stmt
*stmt
) {
149 ok
= SecDbBindInt(stmt
, 1, *version
, &localError
) &&
150 SecDbStep(dbt
, stmt
, NULL
, ^(bool *stop
) {
151 int64_t minor
= sqlite3_column_int64(stmt
, 0);
152 *version
|= ((minor
& 0xff) << 8) | ((minor
& 0xff0000) << 8);
159 secnotice("upgr", "database version is: 0x%08x : %d : %@", *version
, ok
, localError
);
160 CFReleaseSafe(localError
);
167 isClassD(SecDbItemRef item
)
169 CFTypeRef accessible
= SecDbItemGetCachedValueWithName(item
, kSecAttrAccessible
);
171 if (CFEqualSafe(accessible
, kSecAttrAccessibleAlways
) || CFEqualSafe(accessible
, kSecAttrAccessibleAlwaysThisDeviceOnly
))
176 #if TARGET_OS_EMBEDDED
179 measureDuration(struct timeval
*start
)
184 gettimeofday(&stop
, NULL
);
186 duration
= (stop
.tv_sec
-start
->tv_sec
) * 1000;
187 duration
+= (stop
.tv_usec
/ 1000) - (start
->tv_usec
/ 1000);
189 return SecBucket2Significant(duration
);
193 measureUpgradePhase1(struct timeval
*start
, bool success
, int64_t itemsMigrated
)
195 int64_t duration
= measureDuration(start
);
198 SecADSetValueForScalarKey(CFSTR("com.apple.keychain.phase1.migrated-items-success"), itemsMigrated
);
199 SecADSetValueForScalarKey(CFSTR("com.apple.keychain.phase1.migrated-time-success"), duration
);
201 SecADSetValueForScalarKey(CFSTR("com.apple.keychain.phase1.migrated-items-fail"), itemsMigrated
);
202 SecADSetValueForScalarKey(CFSTR("com.apple.keychain.phase1.migrated-time-fail"), duration
);
207 measureUpgradePhase2(struct timeval
*start
, int64_t itemsMigrated
)
209 int64_t duration
= measureDuration(start
);
211 SecADSetValueForScalarKey(CFSTR("com.apple.keychain.phase2.migrated-items"), itemsMigrated
);
212 SecADSetValueForScalarKey(CFSTR("com.apple.keychain.phase2.migrated-time"), duration
);
214 #endif /* TARGET_OS_EMBEDDED */
216 static bool DBClassesAreEqual(const SecDbClass
* class1
, const SecDbClass
* class2
)
218 if (CFEqual(class1
->name
, class2
->name
) && class1
->itemclass
== class2
->itemclass
) {
220 const SecDbAttr
* class1Attr
= class1
->attrs
[attrIndex
];
221 const SecDbAttr
* class2Attr
= class2
->attrs
[attrIndex
];
223 while (class1Attr
&& class2Attr
) {
224 if (CFEqual(class1Attr
->name
, class2Attr
->name
) && class1Attr
->kind
== class2Attr
->kind
&& class1Attr
->flags
== class2Attr
->flags
&& class1Attr
->copyValue
== class2Attr
->copyValue
&& class1Attr
->setValue
== class2Attr
->setValue
) {
226 class1Attr
= class1
->attrs
[attrIndex
];
227 class2Attr
= class2
->attrs
[attrIndex
];
234 // if everything has checked out to this point, and we've hit the end of both class's attr list, then they're equal
235 if (class1Attr
== NULL
&& class2Attr
== NULL
) {
243 static bool ShouldRenameTable(const SecDbClass
* class1
, const SecDbClass
* class2
, int oldTableVersion
)
245 return oldTableVersion
< 10 || !DBClassesAreEqual(class1
, class2
);
248 // Goes through all tables represented by old_schema and tries to migrate all items from them into new (current version) tables.
249 static bool UpgradeSchemaPhase1(SecDbConnectionRef dbt
, const SecDbSchema
*oldSchema
, CFErrorRef
*error
)
251 __block
bool ok
= true;
252 const SecDbSchema
*newSchema
= current_schema();
253 SecDbClass
const *const *oldClass
;
254 SecDbClass
const *const *newClass
;
255 SecDbQueryRef query
= NULL
;
256 CFMutableStringRef sql
= NULL
;
257 SecDbClass
* renamedOldClass
= NULL
;
258 #if TARGET_OS_EMBEDDED
259 __block
int64_t itemsMigrated
= 0;
260 struct timeval start
;
262 gettimeofday(&start
, NULL
);
265 // Rename existing tables to names derived from old schema names
266 sql
= CFStringCreateMutable(NULL
, 0);
267 bool oldClassDone
= false;
268 CFMutableArrayRef classIndexesForNewTables
= CFArrayCreateMutable(NULL
, 0, NULL
);
270 for (oldClass
= oldSchema
->classes
, newClass
= newSchema
->classes
;
271 *newClass
!= NULL
; classIndex
++, oldClass
++, newClass
++) {
272 oldClassDone
|= (*oldClass
) == NULL
; // Check if the new schema has more tables than the old
274 if (!oldClassDone
&& !CFEqual((*oldClass
)->name
, (*newClass
)->name
) && ShouldRenameTable(*oldClass
, *newClass
, oldSchema
->majorVersion
)) {
275 CFStringAppendFormat(sql
, NULL
, CFSTR("ALTER TABLE %@ RENAME TO %@_old;"), (*newClass
)->name
, (*oldClass
)->name
);
276 CFArrayAppendValue(classIndexesForNewTables
, (void*)(long)classIndex
);
278 } else if (!oldClassDone
&& !DBClassesAreEqual(*oldClass
, *newClass
)) {
279 CFStringAppendFormat(sql
, NULL
, CFSTR("ALTER TABLE %@ RENAME TO %@_old;"), (*newClass
)->name
, (*oldClass
)->name
);
280 CFArrayAppendValue(classIndexesForNewTables
, (void*)(long)classIndex
);
283 if(oldClassDone
&& *newClass
) {
284 // These should be no-ops, unless you're upgrading a previously-upgraded database with an invalid version number
285 CFStringAppendFormat(sql
, NULL
, CFSTR("DROP TABLE IF EXISTS %@;"), (*newClass
)->name
);
287 if (classIndexesForNewTables
) {
288 CFArrayAppendValue(classIndexesForNewTables
, (void*)(long)classIndex
);
293 if(CFStringGetLength(sql
) > 0) {
294 require_quiet(ok
&= SecDbExec(dbt
, sql
, error
), out
);
298 // Drop indices that that new schemas will use
299 sql
= CFStringCreateMutable(NULL
, 0);
300 for (newClass
= newSchema
->classes
; *newClass
!= NULL
; newClass
++) {
301 SecDbForEachAttrWithMask((*newClass
),desc
, kSecDbIndexFlag
| kSecDbInFlag
) {
302 CFStringAppendFormat(sql
, 0, CFSTR("DROP INDEX IF EXISTS %@%@;"), (*newClass
)->name
, desc
->name
);
305 require_quiet(ok
&= SecDbExec(dbt
, sql
, error
), out
);
308 // Create tables for new schema.
309 require_quiet(ok
&= SecItemDbCreateSchema(dbt
, newSchema
, classIndexesForNewTables
, false, error
), out
);
310 // Go through all classes of current schema to transfer all items to new tables.
311 for (oldClass
= oldSchema
->classes
, newClass
= newSchema
->classes
;
312 *oldClass
!= NULL
&& *newClass
!= NULL
; oldClass
++, newClass
++) {
314 if (CFEqual((*oldClass
)->name
, (*newClass
)->name
) && DBClassesAreEqual(*oldClass
, *newClass
)) {
318 secnotice("upgr", "Upgrading table %@", (*oldClass
)->name
);
320 // Create a new 'old' class with a new 'old' name.
322 SecDbForEachAttr(*oldClass
, attr
) {
325 if(renamedOldClass
) {
326 CFReleaseNull(renamedOldClass
->name
);
327 free(renamedOldClass
);
329 renamedOldClass
= (SecDbClass
*) malloc(sizeof(SecDbClass
) + sizeof(SecDbAttr
*)*(count
+1));
330 renamedOldClass
->name
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("%@_old"), (*oldClass
)->name
);
331 renamedOldClass
->itemclass
= (*oldClass
)->itemclass
;
332 for(; count
>= 0; count
--) {
333 renamedOldClass
->attrs
[count
] = (*oldClass
)->attrs
[count
];
336 // SecDbItemSelect only works for item classes.
337 if((*oldClass
)->itemclass
) {
338 // Prepare query to iterate through all items in cur_class.
340 query_destroy(query
, NULL
);
341 require_quiet(query
= query_create(renamedOldClass
, SecMUSRGetAllViews(), NULL
, error
), out
);
343 ok
&= SecDbItemSelect(query
, dbt
, error
, ^bool(const SecDbAttr
*attr
) {
344 // We are interested in all attributes which are physically present in the DB.
345 return (attr
->flags
& kSecDbInFlag
) != 0;
346 }, ^bool(const SecDbAttr
*attr
) {
347 // No filtering please.
349 }, NULL
, NULL
, ^(SecDbItemRef item
, bool *stop
) {
350 CFErrorRef localError
= NULL
;
352 #if TARGET_OS_EMBEDDED
355 // Switch item to the new class.
356 item
->class = *newClass
;
358 if (isClassD(item
)) {
360 ok
&= SecDbItemEnsureDecrypted(item
, true, &localError
);
361 require_quiet(ok
, out
);
363 // Delete SHA1 field from the item, so that it is newly recalculated before storing
364 // the item into the new table.
365 require_quiet(ok
&= SecDbItemSetValue(item
, SecDbClassAttrWithKind(item
->class, kSecDbSHA1Attr
, error
),
366 kCFNull
, error
), out
);
368 // Leave item encrypted, do not ever try to decrypt it since it will fail.
369 item
->_edataState
= kSecDbItemAlwaysEncrypted
;
371 // Drop items with kSecAttrAccessGroupToken, as these items should not be there at all. Since agrp attribute
372 // is always stored as cleartext in the DB column, we can always rely on this attribute being present in item->attributes.
373 // <rdar://problem/33401870>
374 if (CFEqualSafe(SecDbItemGetCachedValueWithName(item
, kSecAttrAccessGroup
), kSecAttrAccessGroupToken
) &&
375 SecDbItemGetCachedValueWithName(item
, kSecAttrTokenID
) == NULL
) {
376 secnotice("upgr", "dropping item during schema upgrade due to agrp=com.apple.token: %@", item
);
378 // Insert new item into the new table.
379 if (!SecDbItemInsert(item
, dbt
, &localError
)) {
380 secerror("item: %@ insert during upgrade: %@", item
, localError
);
387 OSStatus status
= SecErrorGetOSStatus(localError
);
390 // continue to upgrade and don't propagate errors for insert failures
391 // that are typical of a single item failure
393 case errSecDuplicateItem
:
396 case errSecInteractionNotAllowed
:
397 case errSecAuthNeeded
:
400 // This does not mean the keychain is hosed, we just can't use it right now
402 case kAKSReturnNotReady
:
403 case kAKSReturnTimeout
:
405 case errSecNotAvailable
:
406 secnotice("upgr", "Bailing in phase 1 because AKS is unavailable: %@", localError
);
409 ok
&= CFErrorPropagate(CFRetainSafe(localError
), error
);
412 CFReleaseSafe(localError
);
418 require_quiet(ok
, out
);
420 // This table does not contain secdb items, and must be transferred without using SecDbItemSelect.
421 // For now, this code does not support removing or renaming any columns, or adding any new non-null columns.
423 sql
= CFStringCreateMutable(NULL
, 0);
426 CFMutableStringRef columns
= CFStringCreateMutable(NULL
, 0);
428 SecDbForEachAttr(renamedOldClass
, attr
) {
430 CFStringAppendFormat(columns
, NULL
, CFSTR(","));
432 CFStringAppendFormat(columns
, NULL
, CFSTR("%@"), attr
->name
);
436 CFStringAppendFormat(sql
, NULL
, CFSTR("INSERT OR REPLACE INTO %@ (%@) SELECT %@ FROM %@;"), (*newClass
)->name
, columns
, columns
, renamedOldClass
->name
);
438 CFReleaseNull(columns
);
439 require_quiet(ok
&= SecDbExec(dbt
, sql
, error
), out
);
443 // Remove old tables from the DB.
445 sql
= CFStringCreateMutable(NULL
, 0);
446 for (oldClass
= oldSchema
->classes
, newClass
= newSchema
->classes
;
447 *oldClass
!= NULL
&& *newClass
!= NULL
; oldClass
++, newClass
++) {
448 if (CFEqual((*oldClass
)->name
, (*newClass
)->name
) && DBClassesAreEqual(*oldClass
, *newClass
)) {
452 CFStringAppendFormat(sql
, NULL
, CFSTR("DROP TABLE %@_old;"), (*oldClass
)->name
);
455 if(CFStringGetLength(sql
) > 0) {
456 require_quiet(ok
&= SecDbExec(dbt
, sql
, error
), out
);
459 #if TARGET_OS_EMBEDDED
460 measureUpgradePhase1(&start
, ok
, SecBucket2Significant(itemsMigrated
));
464 query_destroy(query
, NULL
);
467 CFReleaseNull(classIndexesForNewTables
);
468 if(renamedOldClass
) {
469 CFReleaseNull(renamedOldClass
->name
);
470 free(renamedOldClass
);
475 __thread SecDbConnectionRef dbt
= NULL
;
476 __thread
bool isUnlocked
= false;
478 // Goes through all tables represented by old_schema and tries to migrate all items from them into new (current version) tables.
479 static bool UpgradeItemPhase2(SecDbConnectionRef inDbt
, bool *inProgress
, CFErrorRef
*error
) {
480 SecDbConnectionRef oldDbt
= dbt
;
482 __block
bool ok
= true;
483 SecDbQueryRef query
= NULL
;
484 #if TARGET_OS_EMBEDDED
485 __block
int64_t itemsMigrated
= 0;
486 struct timeval start
;
488 gettimeofday(&start
, NULL
);
491 // Go through all classes in new schema
492 const SecDbSchema
*newSchema
= current_schema();
493 for (const SecDbClass
*const *class = newSchema
->classes
; *class != NULL
&& !*inProgress
; class++) {
494 if(!((*class)->itemclass
)) {
495 //Don't try to decrypt non-item 'classes'
499 const SecDbAttr
*pdmn
= SecDbClassAttrWithKind(*class, kSecDbAccessAttr
, error
);
504 // Prepare query to go through all non-DK|DKU items
506 query_destroy(query
, NULL
);
508 require_action_quiet(query
= query_create(*class, SecMUSRGetAllViews(), NULL
, error
), out
, ok
= false);
509 ok
&= SecDbItemSelect(query
, dbt
, error
, NULL
, ^bool(const SecDbAttr
*attr
) {
510 // No simple per-attribute filtering.
512 }, ^bool(CFMutableStringRef sql
, bool *needWhere
) {
513 // Select only non-D-class items
514 SecDbAppendWhereOrAnd(sql
, needWhere
);
515 CFStringAppendFormat(sql
, NULL
, CFSTR("NOT %@ IN (?,?)"), pdmn
->name
);
517 }, ^bool(sqlite3_stmt
*stmt
, int col
) {
518 return SecDbBindObject(stmt
, col
++, kSecAttrAccessibleAlwaysPrivate
, error
) &&
519 SecDbBindObject(stmt
, col
++, kSecAttrAccessibleAlwaysThisDeviceOnlyPrivate
, error
);
520 }, ^(SecDbItemRef item
, bool *stop
) {
521 CFErrorRef localError
= NULL
;
523 #if TARGET_OS_EMBEDDED
528 if (SecDbItemEnsureDecrypted(item
, true, &localError
)) {
530 // Delete SHA1 field from the item, so that it is newly recalculated before storing
531 // the item into the new table.
532 require_quiet(ok
= SecDbItemSetValue(item
, SecDbClassAttrWithKind(item
->class, kSecDbSHA1Attr
, error
),
533 kCFNull
, &localError
), out
);
534 // Drop items with kSecAttrAccessGroupToken, as these items should not be there at all. Since agrp attribute
535 // is always stored as cleartext in the DB column, we can always rely on this attribute being present in item->attributes.
536 // <rdar://problem/33401870>
537 if (CFEqualSafe(SecDbItemGetCachedValueWithName(item
, kSecAttrAccessGroup
), kSecAttrAccessGroupToken
) &&
538 SecDbItemGetCachedValueWithName(item
, kSecAttrTokenID
) == NULL
) {
539 secnotice("upgr", "dropping item during item upgrade due to agrp=com.apple.token: %@", item
);
540 ok
= SecDbItemDelete(item
, dbt
, kCFBooleanFalse
, &localError
);
542 // Replace item with the new value in the table; this will cause the item to be decoded and recoded back,
543 // incl. recalculation of item's hash.
544 ok
= SecDbItemUpdate(item
, item
, dbt
, false, query
->q_uuid_from_primary_key
, &localError
);
549 CFIndex status
= CFErrorGetCode(localError
);
553 // Items producing errSecDecode are silently dropped - they are not decodable and lost forever.
554 // make sure we use a local error so that this error is not proppaged upward and cause a
555 // migration failure.
556 CFErrorRef deleteError
= NULL
;
557 (void)SecDbItemDelete(item
, dbt
, false, &deleteError
);
558 CFReleaseNull(deleteError
);
562 case errSecInteractionNotAllowed
:
563 // If we are still not able to decrypt the item because the class key is not released yet,
564 // remember that DB still needs phase2 migration to be run next time a connection is made. Also
565 // stop iterating next items, it would be just waste of time because the whole iteration will be run
566 // next time when this phase2 will be rerun.
571 case errSecAuthNeeded
:
572 // errSecAuthNeeded means that it is an ACL-based item which requires authentication (or at least
573 // ACM context, which we do not have).
576 case SQLITE_CONSTRAINT
: // yeah...
577 if (!CFEqual(kSecDbErrorDomain
, CFErrorGetDomain(localError
))) {
578 secerror("Received SQLITE_CONSTRAINT with wrong error domain. Huh? Item: %@, error: %@", item
, localError
);
581 case errSecDuplicateItem
:
582 // continue to upgrade and don't propagate errors for insert failures
583 // that are typical of a single item failure
584 secnotice("upgr", "Ignoring duplicate item: %@", item
);
585 secdebug("upgr", "Duplicate item error: %@", localError
);
589 case kAKSReturnNotReady
:
590 case kAKSReturnTimeout
:
592 case errSecNotAvailable
:
593 *inProgress
= true; // We're not done, call me again later!
594 secnotice("upgr", "Bailing in phase 2 because AKS is unavailable: %@", localError
);
597 // Other errors should abort the migration completely.
598 ok
= CFErrorPropagate(CFRetainSafe(localError
), error
);
604 CFReleaseSafe(localError
);
605 *stop
= *stop
|| !ok
;
611 #if TARGET_OS_EMBEDDED
612 measureUpgradePhase2(&start
, SecBucket2Significant(itemsMigrated
));
617 query_destroy(query
, NULL
);
623 #define SCHEMA_VERSION(schema) ((((schema)->minorVersion) << 8) | ((schema)->majorVersion))
624 #define VERSION_MAJOR(version) ((version) & 0xff)
625 #define VERSION_MINOR(version) (((version) >> 8) & 0xff)
626 #define VERSION_NEW(version) ((version) & 0xffff)
627 #define VERSION_OLD(version) (((version) >> 16) & 0xffff)
629 static bool SecKeychainDbUpgradeFromVersion(SecDbConnectionRef dbt
, int version
, bool *inProgress
, CFErrorRef
*error
) {
630 __block
bool didPhase2
= false;
631 __block
bool ok
= true;
632 __block CFErrorRef localError
= NULL
;
637 const SecDbSchema
*newSchema
= current_schema();
639 // If DB schema is the one we want, we are done.
640 require_quiet(SCHEMA_VERSION(newSchema
) != version
, out
);
642 // Check if the schema of the database on disk is the same major, but newer version then what we have
643 // in code, lets just skip this since a newer version of the OS have upgrade it. Since its the same
644 // major, its a promise that it will be compatible.
645 if (newSchema
->majorVersion
== VERSION_MAJOR(version
) && newSchema
->minorVersion
< VERSION_MINOR(version
)) {
646 secnotice("upgr", "skipping upgrade since minor is newer");
650 if (VERSION_MAJOR(version
) < 6) {
651 // Pre v6 keychains need to have WAL enabled, since SecDb only does this at db creation time.
652 // NOTE: This has to be run outside of a transaction.
653 require_action_quiet(ok
= (SecDbExec(dbt
, CFSTR("PRAGMA auto_vacuum = FULL"), &localError
) &&
654 SecDbExec(dbt
, CFSTR("PRAGMA journal_mode = WAL"), &localError
)),
655 out
, secerror("upgrade: unable to enable WAL or auto vacuum, marking DB as corrupt: %@",
659 ok
&= SecDbTransaction(dbt
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
660 CFStringRef sql
= NULL
;
661 bool didPhase1
= false;
663 // Get version again once we start a transaction, someone else might change the migration state.
665 require_quiet(ok
= SecKeychainDbGetVersion(dbt
, &version2
, &localError
), out
);
666 // Check if someone has raced us to the migration of the database
667 require_action(version
== version2
, out
, CFReleaseNull(localError
); ok
= true);
669 require_quiet(SCHEMA_VERSION(newSchema
) != version2
, out
);
671 // If this is empty database, just create table according to schema and be done with it.
672 require_action_quiet(version2
!= 0, out
, ok
= SecItemDbCreateSchema(dbt
, newSchema
, NULL
, true, &localError
));
674 int oldVersion
= VERSION_OLD(version2
);
675 version2
= VERSION_NEW(version2
);
677 require_action_quiet(version2
== SCHEMA_VERSION(newSchema
) || oldVersion
== 0, out
,
678 ok
= SecDbError(SQLITE_CORRUPT
, &localError
,
679 CFSTR("Half migrated but obsolete DB found: found 0x%x(0x%x) but 0x%x is needed"),
680 version2
, oldVersion
, SCHEMA_VERSION(newSchema
)));
682 // Check whether we have both old and new tables in the DB.
683 if (oldVersion
== 0) {
684 // Pure old-schema migration attempt, with full blown table renames etc (a.k.a. phase1)
685 oldVersion
= version2
;
686 version2
= SCHEMA_VERSION(newSchema
);
688 // Find schema for old database.
689 const SecDbSchema
*oldSchema
= NULL
;
690 for (const SecDbSchema
* const *pschema
= all_schemas(); *pschema
; ++pschema
) {
691 if (SCHEMA_VERSION((*pschema
)) == oldVersion
) {
692 oldSchema
= *pschema
;
697 // If we are attempting to upgrade from a version for which we have no schema, fail.
698 require_action_quiet(oldSchema
!= NULL
, out
,
699 ok
= SecDbError(SQLITE_CORRUPT
, &localError
, CFSTR("no schema for version: 0x%x"), oldVersion
);
700 secerror("no schema for version 0x%x", oldVersion
));
702 secnotice("upgr", "Upgrading from version 0x%x to 0x%x", oldVersion
, SCHEMA_VERSION(newSchema
));
703 SecSignpostStart(SecSignpostUpgradePhase1
);
705 require_action(ok
= UpgradeSchemaPhase1(dbt
, oldSchema
, &localError
), out
, secerror("upgrade: Upgrade phase1 failed: %@", localError
));
706 SecSignpostStop(SecSignpostUpgradePhase1
);
712 CFErrorRef phase2Error
= NULL
;
714 SecSignpostStart(SecSignpostUpgradePhase2
);
716 // Lets try to go through non-D-class items in new tables and apply decode/encode on them
717 // If this fails the error will be ignored after doing a phase1 since but not in the second
718 // time when we are doing phase2.
719 ok
= UpgradeItemPhase2(dbt
, inProgress
, &phase2Error
);
724 CFReleaseNull(phase2Error
);
726 SecErrorPropagate(phase2Error
, &localError
);
729 require_action(ok
, out
, secerror("upgrade: Upgrade phase2 (%d) failed: %@", didPhase1
, localError
));
732 // If either migration path we did reported that the migration was complete, signalize that
733 // in the version database by cleaning oldVersion (which is stored in upper halfword of the version)
734 secnotice("upgr", "Done upgrading from version 0x%x to 0x%x", oldVersion
, SCHEMA_VERSION(newSchema
));
738 SecSignpostStop(SecSignpostUpgradePhase2
);
742 // Update database version table.
743 uint32_t major
= (VERSION_MAJOR(version2
)) | (VERSION_MAJOR(oldVersion
) << 16);
744 uint32_t minor
= (VERSION_MINOR(version2
)) | (VERSION_MINOR(oldVersion
) << 16);
745 secnotice("upgr", "Upgrading saving version major 0x%x minor 0x%x", major
, minor
);
746 sql
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("UPDATE tversion SET version='%d', minor='%d'"),
748 require_action_quiet(ok
= SecDbExec(dbt
, sql
, &localError
), out
, secerror("upgrade: Setting version failed: %@", localError
));
752 secerror("upgrade: SecDB upgrade failed: %@", localError
);
758 if (ok
&& didPhase2
) {
759 #if TARGET_OS_EMBEDDED
760 SecADSetValueForScalarKey(CFSTR("com.apple.keychain.migration-success"), 1);
765 if (!ok
|| localError
) {
766 // TODO: This logic should be inverted to a do-not-corrupt-unless default, <rdar://problem/29771874>
768 * We assume that database is corrupt at this point, but we need to
769 * check if the error we got isn't severe enough to mark the database as corrupt.
770 * In those cases we opt out of corrupting the database.
772 bool markedCorrupt
= true;
775 secwarning("upgrade: error has been set but status is true");
778 secerror("upgrade: error occurred, considering marking database as corrupt: %@", localError
);
780 CFStringRef domain
= CFErrorGetDomain(localError
);
781 CFIndex code
= CFErrorGetCode(localError
);
783 if ((CFEqualSafe(domain
, kSecDbErrorDomain
) &&
784 ((code
& 0xff) == SQLITE_LOCKED
|| (code
& 0xff) == SQLITE_BUSY
|| (code
& 0xff) == SQLITE_FULL
)) ||
786 code
== kAKSReturnNotReady
|| code
== kAKSReturnTimeout
||
788 code
== errSecNotAvailable
)
790 secerror("upgrade: not marking keychain database corrupt for error: %@", localError
);
791 markedCorrupt
= false;
792 CFReleaseNull(localError
);
794 secerror("upgrade: unable to complete upgrade, marking DB as corrupt: %@", localError
);
797 secerror("upgrade: unable to complete upgrade and no error object returned, marking DB as corrupt");
800 secerror("upgrade: marking database as corrupt");
801 SecDbCorrupt(dbt
, localError
);
802 #if TARGET_OS_EMBEDDED
803 SecADSetValueForScalarKey(CFSTR("com.apple.keychain.migration-failure"), 1);
809 *error
= (CFErrorRef
)CFRetain(localError
);
811 CFReleaseNull(localError
);
817 static bool accessGroupIsNetworkExtensionAndClientIsEntitled(CFStringRef accessGroup
, SecurityClient
* client
)
819 return client
&& client
->canAccessNetworkExtensionAccessGroups
&& accessGroup
&& CFStringHasSuffix(accessGroup
, kSecNetworkExtensionAccessGroupSuffix
);
822 /* AUDIT[securityd](done):
823 accessGroup (ok) is a caller provided, non NULL CFTypeRef.
825 Return true iff accessGroup is allowable according to accessGroups.
827 bool accessGroupsAllows(CFArrayRef accessGroups
, CFStringRef accessGroup
, SecurityClient
* client
) {
828 /* NULL accessGroups is wildcard. */
831 /* Make sure we have a string. */
832 if (!isString(accessGroup
))
835 /* Having the special accessGroup "*" allows access to all accessGroups. */
836 CFRange range
= { 0, CFArrayGetCount(accessGroups
) };
838 (CFArrayContainsValue(accessGroups
, range
, accessGroup
) ||
839 CFArrayContainsValue(accessGroups
, range
, CFSTR("*")) ||
840 accessGroupIsNetworkExtensionAndClientIsEntitled(accessGroup
, client
)))
846 bool itemInAccessGroup(CFDictionaryRef item
, CFArrayRef accessGroups
) {
847 return accessGroupsAllows(accessGroups
,
848 CFDictionaryGetValue(item
, kSecAttrAccessGroup
), NULL
);
852 static CF_RETURNS_RETAINED CFDataRef
SecServerExportBackupableKeychain(SecDbConnectionRef dbt
,
853 SecurityClient
*client
,
854 keybag_handle_t src_keybag
, keybag_handle_t dest_keybag
, CFErrorRef
*error
) {
855 CFDataRef data_out
= NULL
;
857 SecSignpostStart(SecSignpostBackupKeychainBackupable
);
859 /* Export everything except the items for which SecItemIsSystemBound()
861 CFDictionaryRef keychain
= SecServerCopyKeychainPlist(dbt
, client
,
862 src_keybag
, dest_keybag
, kSecBackupableItemFilter
,
865 data_out
= CFPropertyListCreateData(kCFAllocatorDefault
, keychain
,
866 kCFPropertyListBinaryFormat_v1_0
,
870 SecSignpostStop(SecSignpostBackupKeychainBackupable
);
875 static bool SecServerImportBackupableKeychain(SecDbConnectionRef dbt
,
876 SecurityClient
*client
,
877 keybag_handle_t src_keybag
,
878 keybag_handle_t dest_keybag
,
882 return kc_transaction(dbt
, error
, ^{
884 CFDictionaryRef keychain
;
886 SecSignpostStart(SecSignpostRestoreKeychainBackupable
);
888 keychain
= CFPropertyListCreateWithData(kCFAllocatorDefault
, data
,
889 kCFPropertyListImmutable
, NULL
,
892 if (isDictionary(keychain
)) {
893 ok
= SecServerImportKeychainInPlist(dbt
,
898 kSecBackupableItemFilter
,
899 false, // Restoring backup should not remove stuff that got into the keychain before us
902 ok
= SecError(errSecParam
, error
, CFSTR("import: keychain is not a dictionary"));
907 SecSignpostStop(SecSignpostRestoreKeychainBackupable
);
915 * Similar to ks_open_keybag, but goes through MKB interface
917 static bool mkb_open_keybag(CFDataRef keybag
, CFDataRef password
, MKBKeyBagHandleRef
*handle
, bool emcs
, CFErrorRef
*error
) {
919 MKBKeyBagHandleRef mkbhandle
= NULL
;
921 rc
= MKBKeyBagCreateWithData(keybag
, &mkbhandle
);
922 if (rc
!= kMobileKeyBagSuccess
) {
923 return SecKernError(rc
, error
, CFSTR("MKBKeyBagCreateWithData failed: %d"), rc
);
927 rc
= MKBKeyBagUnlock(mkbhandle
, password
);
928 if (rc
!= kMobileKeyBagSuccess
) {
929 CFRelease(mkbhandle
);
930 return SecKernError(rc
, error
, CFSTR("failed to unlock bag: %d"), rc
);
933 secnotice("keychainbackup", "skipping keybag unlock for EMCS");
943 static CFDataRef
SecServerKeychainCreateBackup(SecDbConnectionRef dbt
, SecurityClient
*client
, CFDataRef keybag
,
944 CFDataRef password
, bool emcs
, CFErrorRef
*error
) {
945 CFDataRef backup
= NULL
;
946 keybag_handle_t backup_keybag
;
948 SecSignpostStart(SecSignpostBackupOpenKeybag
);
951 MKBKeyBagHandleRef mkbhandle
= NULL
;
952 require(mkb_open_keybag(keybag
, password
, &mkbhandle
, emcs
, error
), out
);
954 require_noerr(MKBKeyBagGetAKSHandle(mkbhandle
, &backup_keybag
), out
);
957 backup_keybag
= KEYBAG_NONE
;
959 SecSignpostStop(SecSignpostBackupOpenKeybag
);
960 SecSignpostStart(SecSignpostBackupKeychain
);
962 /* Export from system keybag to backup keybag. */
963 backup
= SecServerExportBackupableKeychain(dbt
, client
, KEYBAG_DEVICE
, backup_keybag
, error
);
967 SecSignpostStop(SecSignpostBackupOpenKeybag
);
970 CFRelease(mkbhandle
);
975 static bool SecServerKeychainRestore(SecDbConnectionRef dbt
,
976 SecurityClient
*client
,
983 keybag_handle_t backup_keybag
;
986 SecSignpostStart(SecSignpostRestoreOpenKeybag
);
988 MKBKeyBagHandleRef mkbhandle
= NULL
;
989 require(mkb_open_keybag(keybag
, password
, &mkbhandle
, false, error
), out
);
991 require_noerr(MKBKeyBagGetAKSHandle(mkbhandle
, &backup_keybag
), out
);
993 backup_keybag
= KEYBAG_NONE
;
995 SecSignpostStop(SecSignpostRestoreOpenKeybag
);
996 SecSignpostStart(SecSignpostRestoreKeychain
);
998 /* Import from backup keybag to system keybag. */
999 require(SecServerImportBackupableKeychain(dbt
, client
, backup_keybag
, KEYBAG_DEVICE
, backup
, error
), out
);
1003 SecSignpostStop(SecSignpostRestoreKeychain
);
1006 CFRelease(mkbhandle
);
1010 secwarning("Restore completed sucessfully");
1012 secwarning("Restore failed with: %@", error
? *error
: NULL
);
1019 // MARK - External SPI support code.
1021 CFStringRef
__SecKeychainCopyPath(void) {
1022 CFStringRef kcRelPath
= NULL
;
1024 kcRelPath
= CFSTR("keychain-2.db");
1026 kcRelPath
= CFSTR("keychain-2-debug.db");
1029 CFStringRef kcPath
= NULL
;
1030 CFURLRef kcURL
= SecCopyURLForFileInKeychainDirectory(kcRelPath
);
1032 kcPath
= CFURLCopyFileSystemPath(kcURL
, kCFURLPOSIXPathStyle
);
1039 // MARK: kc_dbhandle init and reset
1041 SecDbRef
SecKeychainDbCreate(CFStringRef path
, CFErrorRef
* error
) {
1042 __block CFErrorRef localerror
= NULL
;
1044 SecDbRef kc
= SecDbCreate(path
, ^bool (SecDbRef db
, SecDbConnectionRef dbconn
, bool didCreate
, bool *callMeAgainForNextConnection
, CFErrorRef
*error
) {
1045 // Upgrade from version 0 means create the schema in empty db.
1049 ok
= SecKeychainDbGetVersion(dbconn
, &version
, error
);
1051 ok
= ok
&& SecKeychainDbUpgradeFromVersion(dbconn
, version
, callMeAgainForNextConnection
, error
);
1053 secerror("Upgrade %sfailed: %@", didCreate
? "from v0 " : "", error
? *error
: NULL
);
1055 localerror
= error
? *error
: NULL
;
1058 // This block might get called many, many times due to callMeAgainForNextConnection.
1059 // When we no longer want to be called, we believe we're done. Begin the rest of initialization.
1060 if( !callMeAgainForNextConnection
|| !(*callMeAgainForNextConnection
)) {
1061 SecKeychainDbInitialize(db
);
1069 SecDbSetCorruptionReset(kc
, ^{
1070 SecDbResetMetadataKeys();
1075 *error
= localerror
;
1081 SecDbRef
SecKeychainDbInitialize(SecDbRef db
) {
1084 if(SecCKKSIsEnabled()) {
1085 // This needs to be async, otherwise we get hangs between securityd, cloudd, and apsd
1086 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT
, 0), ^{
1087 SecCKKSInitialize(db
);
1096 static SecDbRef _kc_dbhandle
= NULL
;
1097 static dispatch_queue_t _kc_dbhandle_dispatch
= NULL
;
1098 static dispatch_once_t _kc_dbhandle_dispatch_onceToken
= 0;
1099 static dispatch_queue_t
get_kc_dbhandle_dispatch() {
1100 dispatch_once(&_kc_dbhandle_dispatch_onceToken
, ^{
1101 _kc_dbhandle_dispatch
= dispatch_queue_create("sec_kc_dbhandle", DISPATCH_QUEUE_SERIAL
);
1104 return _kc_dbhandle_dispatch
;
1107 static bool kc_dbhandle_init(CFErrorRef
* error
) {
1108 SecDbRef oldHandle
= _kc_dbhandle
;
1109 _kc_dbhandle
= NULL
;
1110 CFStringRef dbPath
= __SecKeychainCopyPath();
1112 _kc_dbhandle
= SecKeychainDbCreate(dbPath
, error
);
1115 secerror("no keychain path available");
1118 secerror("replaced %@ with %@", oldHandle
, _kc_dbhandle
);
1119 CFRelease(oldHandle
);
1121 // Having a dbhandle means we succeeded.
1122 return !!_kc_dbhandle
;
1125 static SecDbRef
kc_dbhandle(CFErrorRef
* error
)
1127 dispatch_sync(get_kc_dbhandle_dispatch(), ^{
1128 if(_kc_dbhandle
== NULL
) {
1129 _SecDbServerSetup();
1130 kc_dbhandle_init(error
);
1133 return _kc_dbhandle
;
1136 /* For whitebox testing only */
1137 void SecKeychainDbReset(dispatch_block_t inbetween
)
1139 dispatch_sync(get_kc_dbhandle_dispatch(), ^{
1140 CFReleaseNull(_kc_dbhandle
);
1141 SecDbResetMetadataKeys();
1148 static bool checkIsUnlocked()
1150 CFErrorRef aksError
= NULL
;
1153 if(!SecAKSGetIsLocked(&locked
, &aksError
)) {
1154 secerror("error querying lock state: %@", aksError
);
1155 CFReleaseNull(aksError
);
1161 static bool kc_acquire_dbt(bool writeAndRead
, SecDbConnectionRef
* dbconn
, CFErrorRef
*error
) {
1162 SecDbRef db
= kc_dbhandle(error
);
1164 if(error
&& !(*error
)) {
1165 SecError(errSecDataNotAvailable
, error
, CFSTR("failed to get a db handle"));
1170 return SecDbConnectionAcquireRefMigrationSafe(db
, !writeAndRead
, dbconn
, error
);
1173 /* Return a per thread dbt handle for the keychain. If create is true create
1174 the database if it does not yet exist. If it is false, just return an
1175 error if it fails to auto-create. */
1176 bool kc_with_dbt(bool writeAndRead
, CFErrorRef
*error
, bool (^perform
)(SecDbConnectionRef dbt
))
1178 return kc_with_custom_db(writeAndRead
, true, NULL
, error
, perform
);
1181 bool kc_with_dbt_non_item_tables(bool writeAndRead
, CFErrorRef
* error
, bool (^perform
)(SecDbConnectionRef dbt
))
1183 return kc_with_custom_db(writeAndRead
, false, NULL
, error
, perform
);
1186 bool kc_with_custom_db(bool writeAndRead
, bool usesItemTables
, SecDbRef db
, CFErrorRef
*error
, bool (^perform
)(SecDbConnectionRef dbt
))
1188 if (db
&& db
!= kc_dbhandle(error
)) {
1189 __block
bool result
= false;
1191 return SecDbPerformWrite(db
, error
, ^(SecDbConnectionRef dbconn
) {
1192 result
= perform(dbconn
);
1196 return SecDbPerformRead(db
, error
, ^(SecDbConnectionRef dbconn
) {
1197 result
= perform(dbconn
);
1204 // The kc_with_dbt upthread will clean this up when it's done.
1205 return perform(dbt
);
1208 if (writeAndRead
&& usesItemTables
) {
1209 SecItemDataSourceFactoryGetDefault();
1213 if (kc_acquire_dbt(writeAndRead
, &dbt
, error
)) {
1214 isUnlocked
= checkIsUnlocked();
1216 SecDbConnectionRelease(dbt
);
1223 bool kc_is_unlocked()
1225 return isUnlocked
|| checkIsUnlocked();
1229 items_matching_issuer_parent(SecDbConnectionRef dbt
, CFArrayRef accessGroups
, CFDataRef musrView
,
1230 CFDataRef issuer
, CFArrayRef issuers
, int recurse
)
1233 CFArrayRef results
= NULL
;
1237 if (CFArrayContainsValue(issuers
, CFRangeMake(0, CFArrayGetCount(issuers
)), issuer
))
1240 /* XXX make musr supported */
1241 const void *keys
[] = { kSecClass
, kSecReturnRef
, kSecAttrSubject
};
1242 const void *vals
[] = { kSecClassCertificate
, kCFBooleanTrue
, issuer
};
1243 CFDictionaryRef query
= CFDictionaryCreate(kCFAllocatorDefault
, keys
, vals
, array_size(keys
), NULL
, NULL
);
1248 CFErrorRef localError
= NULL
;
1249 q
= query_create_with_limit(query
, musrView
, kSecMatchUnlimited
, &localError
);
1252 s3dl_copy_matching(dbt
, q
, (CFTypeRef
*)&results
, accessGroups
, &localError
);
1253 query_destroy(q
, &localError
);
1256 secerror("items matching issuer parent: %@", localError
);
1257 CFReleaseNull(localError
);
1261 count
= CFArrayGetCount(results
);
1262 for (i
= 0; (i
< count
) && !found
; i
++) {
1263 CFDictionaryRef cert_dict
= (CFDictionaryRef
)CFArrayGetValueAtIndex(results
, i
);
1264 CFDataRef cert_issuer
= CFDictionaryGetValue(cert_dict
, kSecAttrIssuer
);
1265 if (CFEqual(cert_issuer
, issuer
))
1268 found
= items_matching_issuer_parent(dbt
, accessGroups
, musrView
, cert_issuer
, issuers
, recurse
);
1270 CFReleaseSafe(results
);
1276 _FilterWithPolicy(SecPolicyRef policy
, CFDateRef date
, SecCertificateRef cert
)
1278 CFDictionaryRef props
= NULL
;
1279 CFArrayRef keychains
= NULL
;
1280 CFArrayRef anchors
= NULL
;
1281 CFArrayRef certs
= NULL
;
1282 CFArrayRef chain
= NULL
;
1283 SecTrustRef trust
= NULL
;
1285 SecTrustResultType trustResult
;
1286 Boolean needChain
= false;
1287 __block
bool ok
= false;
1289 if (!policy
|| !cert
) return false;
1291 certs
= CFArrayCreate(NULL
, (const void **)&cert
, (CFIndex
)1, &kCFTypeArrayCallBacks
);
1292 require_noerr_quiet(SecTrustCreateWithCertificates(certs
, policy
, &trust
), cleanup
);
1294 /* Set evaluation date, if specified (otherwise current date is implied) */
1295 if (date
&& (CFGetTypeID(date
) == CFDateGetTypeID())) {
1296 require_noerr_quiet(SecTrustSetVerifyDate(trust
, date
), cleanup
);
1299 /* Check whether this is the X509 Basic policy, which means chain building */
1300 props
= SecPolicyCopyProperties(policy
);
1302 CFTypeRef oid
= (CFTypeRef
) CFDictionaryGetValue(props
, kSecPolicyOid
);
1303 if (oid
&& (CFEqual(oid
, kSecPolicyAppleX509Basic
) ||
1304 CFEqual(oid
, kSecPolicyAppleRevocation
))) {
1310 require_noerr_quiet(SecTrustEvaluateLeafOnly(trust
, &trustResult
), cleanup
);
1312 require_noerr_quiet(SecTrustEvaluate(trust
, &trustResult
), cleanup
);
1315 require_quiet((trustResult
== kSecTrustResultProceed
||
1316 trustResult
== kSecTrustResultUnspecified
||
1317 trustResult
== kSecTrustResultRecoverableTrustFailure
), cleanup
);
1320 #if TARGET_OS_IPHONE
1321 CFArrayRef properties
= SecTrustCopyProperties(trust
);
1323 CFArrayRef properties
= SecTrustCopyProperties_ios(trust
);
1326 CFArrayForEach(properties
, ^(const void *property
) {
1327 CFDictionaryForEach((CFDictionaryRef
)property
, ^(const void *key
, const void *value
) {
1328 if (CFEqual((CFTypeRef
)key
, kSecPropertyKeyType
) && CFEqual((CFTypeRef
)value
, kSecPropertyTypeError
))
1332 CFRelease(properties
);
1336 if(props
) CFRelease(props
);
1337 if(chain
) CFRelease(chain
);
1338 if(anchors
) CFRelease(anchors
);
1339 if(keychains
) CFRelease(keychains
);
1340 if(certs
) CFRelease(certs
);
1341 if(trust
) CFRelease(trust
);
1347 _FilterWithDate(CFDateRef validOnDate
, SecCertificateRef cert
)
1349 if (!validOnDate
|| !cert
) return false;
1351 CFAbsoluteTime at
, nb
, na
;
1352 at
= CFDateGetAbsoluteTime((CFDateRef
)validOnDate
);
1355 nb
= SecCertificateNotValidBefore(cert
);
1356 na
= SecCertificateNotValidAfter(cert
);
1358 if (nb
== 0 || na
== 0 || nb
== na
) {
1360 secnotice("FilterWithDate", "certificate cannot operate");
1364 secnotice("FilterWithDate", "certificate is not valid yet");
1368 secnotice("FilterWithDate", "certificate expired");
1375 _FilterWithTrust(Boolean trustedOnly
, SecCertificateRef cert
)
1377 if (!cert
) return false;
1378 if (!trustedOnly
) return true;
1381 CFArrayRef certArray
= CFArrayCreate(NULL
, (const void**)&cert
, 1, &kCFTypeArrayCallBacks
);
1382 SecTrustRef trust
= NULL
;
1383 SecPolicyRef policy
= SecPolicyCreateBasicX509();
1384 require_quiet(policy
, out
);
1386 require_noerr_quiet(SecTrustCreateWithCertificates(certArray
, policy
, &trust
), out
);
1387 SecTrustResultType trustResult
;
1388 require_noerr_quiet(SecTrustEvaluate(trust
, &trustResult
), out
);
1390 require_quiet((trustResult
== kSecTrustResultProceed
||
1391 trustResult
== kSecTrustResultUnspecified
), out
);
1394 CFReleaseSafe(trust
);
1395 CFReleaseSafe(policy
);
1396 CFReleaseSafe(certArray
);
1400 static SecCertificateRef
1401 CopyCertificateFromItem(Query
*q
, CFDictionaryRef item
) {
1402 SecCertificateRef certRef
= NULL
;
1403 CFDictionaryRef itemValue
= NULL
;
1405 CFTypeRef tokenID
= NULL
;
1406 CFDataRef certData
= NULL
;
1407 if (q
->q_class
== identity_class()) {
1408 certData
= CFDictionaryGetValue(item
, kSecAttrIdentityCertificateData
);
1409 tokenID
= CFDictionaryGetValue(item
, kSecAttrIdentityCertificateTokenID
);
1410 } else if (q
->q_class
== cert_class()) {
1411 certData
= CFDictionaryGetValue(item
, kSecValueData
);
1412 tokenID
= CFDictionaryGetValue(item
, kSecAttrTokenID
);
1415 require_quiet(certData
, out
);
1416 if (tokenID
!= NULL
) {
1417 CFErrorRef error
= NULL
;
1418 itemValue
= SecTokenItemValueCopy(certData
, &error
);
1419 require_action_quiet(itemValue
, out
, { secerror("function SecTokenItemValueCopy failed with: %@", error
); CFReleaseSafe(error
); });
1420 CFDataRef tokenCertData
= CFDictionaryGetValue(itemValue
, kSecTokenValueDataKey
);
1421 require_action_quiet(tokenCertData
, out
, { secerror("token item doesn't contain token value data");});
1422 certRef
= SecCertificateCreateWithData(kCFAllocatorDefault
, tokenCertData
);
1425 certRef
= SecCertificateCreateWithData(kCFAllocatorDefault
, certData
);
1428 CFReleaseNull(itemValue
);
1432 bool match_item(SecDbConnectionRef dbt
, Query
*q
, CFArrayRef accessGroups
, CFDictionaryRef item
)
1435 SecCertificateRef certRef
= NULL
;
1436 if (q
->q_match_issuer
) {
1437 CFDataRef issuer
= CFDictionaryGetValue(item
, kSecAttrIssuer
);
1438 if (!items_matching_issuer_parent(dbt
, accessGroups
, q
->q_musrView
, issuer
, q
->q_match_issuer
, 10 /*max depth*/))
1442 if (q
->q_match_policy
&& (q
->q_class
== identity_class() || q
->q_class
== cert_class())) {
1444 certRef
= CopyCertificateFromItem(q
, item
);
1445 require_quiet(certRef
, out
);
1446 require_quiet(_FilterWithPolicy(q
->q_match_policy
, q
->q_match_valid_on_date
, certRef
), out
);
1449 if (q
->q_match_valid_on_date
&& (q
->q_class
== identity_class() || q
->q_class
== cert_class())) {
1451 certRef
= CopyCertificateFromItem(q
, item
);
1452 require_quiet(certRef
, out
);
1453 require_quiet(_FilterWithDate(q
->q_match_valid_on_date
, certRef
), out
);
1456 if (q
->q_match_trusted_only
&& (q
->q_class
== identity_class() || q
->q_class
== cert_class())) {
1458 certRef
= CopyCertificateFromItem(q
, item
);
1459 require_quiet(certRef
, out
);
1460 require_quiet(_FilterWithTrust(CFBooleanGetValue(q
->q_match_trusted_only
), certRef
), out
);
1463 /* Add future match checks here. */
1466 CFReleaseSafe(certRef
);
1470 /****************************************************************************
1471 **************** Beginning of Externally Callable Interface ****************
1472 ****************************************************************************/
1474 void (*SecTaskDiagnoseEntitlements
)(CFArrayRef accessGroups
) = NULL
;
1476 static bool SecEntitlementError(OSStatus status
, CFErrorRef
*error
)
1479 #define SEC_ENTITLEMENT_WARNING CFSTR("com.apple.application-identifier, com.apple.security.application-groups nor keychain-access-groups")
1481 #define SEC_ENTITLEMENT_WARNING CFSTR("application-identifier nor keychain-access-groups")
1484 return SecError(errSecMissingEntitlement
, error
, CFSTR("Client has neither %@ entitlements"), SEC_ENTITLEMENT_WARNING
);
1487 static CFStringRef
CopyAccessGroupForRowID(sqlite_int64 rowID
, CFStringRef itemClass
)
1489 __block CFStringRef accessGroup
= NULL
;
1491 __block CFErrorRef error
= NULL
;
1492 bool ok
= kc_with_dbt(false, &error
, ^bool(SecDbConnectionRef dbt
) {
1493 CFStringRef table
= CFEqual(itemClass
, kSecClassIdentity
) ? kSecClassCertificate
: itemClass
;
1494 CFStringRef sql
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("SELECT agrp FROM %@ WHERE rowid == %u"), table
, (unsigned int)rowID
);
1495 bool dbOk
= SecDbWithSQL(dbt
, sql
, &error
, ^bool(sqlite3_stmt
*stmt
) {
1496 bool rowOk
= SecDbForEach(dbt
, stmt
, &error
, ^bool(int row_index
) {
1497 accessGroup
= CFStringCreateWithBytes(NULL
, sqlite3_column_blob(stmt
, 0), sqlite3_column_bytes(stmt
, 0), kCFStringEncodingUTF8
, false);
1498 return accessGroup
!= NULL
;
1501 return (bool)(rowOk
&& accessGroup
!= NULL
);
1505 return (bool)(dbOk
&& accessGroup
);
1512 CFReleaseNull(accessGroup
);
1517 /* AUDIT[securityd](done):
1518 query (ok) is a caller provided dictionary, only its cf type has been checked.
1521 SecItemServerCopyMatching(CFDictionaryRef query
, CFTypeRef
*result
,
1522 SecurityClient
*client
, CFErrorRef
*error
)
1524 CFArrayRef accessGroups
= client
->accessGroups
;
1525 CFMutableArrayRef mutableAccessGroups
= NULL
;
1528 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
))) {
1529 if (SecTaskDiagnoseEntitlements
)
1530 SecTaskDiagnoseEntitlements(accessGroups
);
1531 return SecEntitlementError(errSecMissingEntitlement
, error
);
1534 SecSignpostStart(SecSignpostSecItemCopyMatching
);
1536 if (client
->canAccessNetworkExtensionAccessGroups
) {
1537 CFDataRef persistentRef
= CFDictionaryGetValue(query
, kSecValuePersistentRef
);
1538 CFStringRef itemClass
= NULL
;
1539 sqlite_int64 itemRowID
= 0;
1540 if (persistentRef
&& _SecItemParsePersistentRef(persistentRef
, &itemClass
, &itemRowID
, NULL
)) {
1541 CFStringRef accessGroup
= CopyAccessGroupForRowID(itemRowID
, itemClass
);
1542 if (accessGroup
&& CFStringHasSuffix(accessGroup
, kSecNetworkExtensionAccessGroupSuffix
) && !CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), accessGroup
)) {
1543 mutableAccessGroups
= CFArrayCreateMutableCopy(NULL
, 0, accessGroups
);
1544 CFArrayAppendValue(mutableAccessGroups
, accessGroup
);
1545 accessGroups
= mutableAccessGroups
;
1547 CFReleaseNull(accessGroup
);
1551 if (CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), CFSTR("*"))) {
1552 /* Having the special accessGroup "*" allows access to all accessGroups. */
1553 accessGroups
= NULL
;
1557 Query
*q
= query_create_with_limit(query
, client
->musr
, 1, error
);
1559 CFStringRef agrp
= CFDictionaryGetValue(q
->q_item
, kSecAttrAccessGroup
);
1560 if (agrp
&& accessGroupsAllows(accessGroups
, agrp
, client
)) {
1561 // TODO: Return an error if agrp is not NULL and accessGroupsAllows() fails above.
1562 const void *val
= agrp
;
1563 accessGroups
= CFArrayCreate(0, &val
, 1, &kCFTypeArrayCallBacks
);
1565 CFRetainSafe(accessGroups
);
1568 #if TARGET_OS_IPHONE
1569 if (q
->q_sync_bubble
&& client
->inMultiUser
) {
1570 CFReleaseNull(q
->q_musrView
);
1571 q
->q_musrView
= SecMUSRCreateSyncBubbleUserUUID(q
->q_sync_bubble
);
1572 } else if (client
->inMultiUser
&& client
->isNetworkExtension
) {
1573 CFReleaseNull(q
->q_musrView
);
1574 q
->q_musrView
= SecMUSRCreateBothUserAndSystemUUID(client
->uid
);
1575 } else if (q
->q_system_keychain
&& client
->inMultiUser
) {
1576 CFReleaseNull(q
->q_musrView
);
1577 q
->q_musrView
= SecMUSRCopySystemKeychainUUID();
1579 q
->q_system_keychain
= false;
1583 query_set_caller_access_groups(q
, accessGroups
);
1585 /* Sanity check the query. */
1586 if (q
->q_system_keychain
&& !client
->allowSystemKeychain
) {
1587 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for system keychain"));
1588 } else if (q
->q_sync_bubble
&& !client
->allowSyncBubbleKeychain
) {
1589 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for syncbubble keychain"));
1590 } else if (q
->q_system_keychain
&& q
->q_sync_bubble
) {
1591 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("can't do both system and syncbubble keychain"));
1592 } else if (q
->q_use_item_list
) {
1593 ok
= SecError(errSecUseItemListUnsupported
, error
, CFSTR("use item list unsupported"));
1594 } else if (q
->q_match_issuer
&& ((q
->q_class
!= cert_class()) &&
1595 (q
->q_class
!= identity_class()))) {
1596 ok
= SecError(errSecUnsupportedOperation
, error
, CFSTR("unsupported match attribute"));
1597 } else if (q
->q_match_policy
&& ((q
->q_class
!= cert_class()) &&
1598 (q
->q_class
!= identity_class()))) {
1599 ok
= SecError(errSecUnsupportedOperation
, error
, CFSTR("unsupported kSecMatchPolicy attribute"));
1600 } else if (q
->q_return_type
!= 0 && result
== NULL
) {
1601 ok
= SecError(errSecReturnMissingPointer
, error
, CFSTR("missing pointer"));
1602 } else if (!q
->q_error
) {
1603 ok
= kc_with_dbt(false, error
, ^(SecDbConnectionRef dbt
) {
1604 return s3dl_copy_matching(dbt
, q
, result
, accessGroups
, error
);
1608 CFReleaseSafe(accessGroups
);
1609 if (!query_destroy(q
, error
))
1612 CFReleaseNull(mutableAccessGroups
);
1614 SecSignpostStop(SecSignpostSecItemCopyMatching
);
1620 _SecItemCopyMatching(CFDictionaryRef query
, SecurityClient
*client
, CFTypeRef
*result
, CFErrorRef
*error
) {
1621 return SecItemServerCopyMatching(query
, result
, client
, error
);
1624 #if TARGET_OS_IPHONE
1626 SecItemSynchronizable(CFDictionaryRef query
)
1628 bool result
= false;
1629 CFTypeRef value
= CFDictionaryGetValue(query
, kSecAttrSynchronizable
);
1630 if (isBoolean(value
))
1631 return CFBooleanGetValue(value
);
1632 else if (isNumber(value
)) {
1634 (void)CFNumberGetValue(value
, kCFNumberSInt32Type
, &number
);
1643 /* AUDIT[securityd](done):
1644 attributes (ok) is a caller provided dictionary, only its cf type has
1648 _SecItemAdd(CFDictionaryRef attributes
, SecurityClient
*client
, CFTypeRef
*result
, CFErrorRef
*error
)
1650 CFArrayRef accessGroups
= client
->accessGroups
;
1654 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
))) {
1655 if (SecTaskDiagnoseEntitlements
)
1656 SecTaskDiagnoseEntitlements(accessGroups
);
1657 return SecEntitlementError(errSecMissingEntitlement
, error
);
1660 SecSignpostStart(SecSignpostSecItemAdd
);
1662 Query
*q
= query_create_with_limit(attributes
, client
->musr
, 0, error
);
1664 /* Access group sanity checking. */
1665 CFStringRef agrp
= (CFStringRef
)CFDictionaryGetValue(attributes
,
1666 kSecAttrAccessGroup
);
1668 /* Having the special accessGroup "*" allows access to all accessGroups. */
1669 if (CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), CFSTR("*")))
1670 accessGroups
= NULL
;
1673 /* The user specified an explicit access group, validate it. */
1674 if (!accessGroupsAllows(accessGroups
, agrp
, client
))
1675 ok
= SecError(errSecMissingEntitlement
, error
,
1676 CFSTR("explicit accessGroup %@ not in client access %@"), agrp
, accessGroups
);
1678 agrp
= (CFStringRef
)CFArrayGetValueAtIndex(client
->accessGroups
, 0);
1680 /* We are using an implicit access group, add it as if the user
1681 specified it as an attribute. */
1682 query_add_attribute(kSecAttrAccessGroup
, agrp
, q
);
1685 if (CFEqual(agrp
, kSecAttrAccessGroupToken
)) {
1686 ok
= SecError(errSecParam
, error
, CFSTR("storing items into kSecAttrAccessGroupToken is not allowed"));
1689 if (SecPLShouldLogRegisteredEvent(CFSTR("SecItem"))) {
1690 CFDictionaryRef dict
= CFDictionaryCreateForCFTypes(NULL
, CFSTR("operation"), CFSTR("add"), CFSTR("AccessGroup"), agrp
, NULL
);
1692 SecPLLogRegisteredEvent(CFSTR("SecItem"), dict
);
1696 #if TARGET_OS_IPHONE
1697 if (q
->q_system_keychain
&& client
->inMultiUser
) {
1698 CFReleaseNull(q
->q_musrView
);
1699 q
->q_musrView
= SecMUSRCopySystemKeychainUUID();
1701 q
->q_system_keychain
= false;
1703 query_add_attribute_with_desc(&v8musr
, q
->q_musrView
, q
);
1707 query_ensure_access_control(q
, agrp
);
1710 void (^add_sync_callback
)(bool, CFErrorRef
) = CFDictionaryGetValue(attributes
, CFSTR("f_ckkscallback"));
1711 if(add_sync_callback
) {
1712 // The existence of this callback indicates that we need a predictable UUID for this item.
1713 q
->q_uuid_from_primary_key
= true;
1714 q
->q_add_sync_callback
= add_sync_callback
;
1718 if (q
->q_system_keychain
&& !client
->allowSystemKeychain
) {
1719 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for system keychain"));
1720 } else if (q
->q_sync_bubble
&& !client
->allowSyncBubbleKeychain
) {
1721 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for syncbubble keychain"));
1722 #if TARGET_OS_IPHONE
1723 } else if (q
->q_system_keychain
&& SecItemSynchronizable(attributes
) && !client
->inMultiUser
) {
1724 ok
= SecError(errSecInvalidKey
, error
, CFSTR("Can't store system keychain and synchronizable"));
1726 } else if (q
->q_row_id
|| q
->q_token_object_id
) {
1727 ok
= SecError(errSecValuePersistentRefUnsupported
, error
, CFSTR("q_row_id")); // TODO: better error string
1728 } else if (!q
->q_error
) {
1729 ok
= kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
){
1730 return kc_transaction(dbt
, error
, ^{
1731 query_pre_add(q
, true);
1732 return s3dl_query_add(dbt
, q
, result
, error
);
1737 ok
= query_notify_and_destroy(q
, ok
, error
);
1742 SecSignpostStop(SecSignpostSecItemAdd
);
1747 /* AUDIT[securityd](done):
1748 query (ok) and attributesToUpdate (ok) are a caller provided dictionaries,
1749 only their cf types have been checked.
1752 _SecItemUpdate(CFDictionaryRef query
, CFDictionaryRef attributesToUpdate
,
1753 SecurityClient
*client
, CFErrorRef
*error
)
1755 CFArrayRef accessGroups
= client
->accessGroups
;
1758 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
))) {
1759 if (SecTaskDiagnoseEntitlements
)
1760 SecTaskDiagnoseEntitlements(accessGroups
);
1761 return SecEntitlementError(errSecMissingEntitlement
, error
);
1764 SecSignpostStart(SecSignpostSecItemUpdate
);
1766 if (SecPLShouldLogRegisteredEvent(CFSTR("SecItem"))) {
1767 CFTypeRef agrp
= CFArrayGetValueAtIndex(accessGroups
, 0);
1768 CFDictionaryRef dict
= CFDictionaryCreateForCFTypes(NULL
, CFSTR("operation"), CFSTR("update"), CFSTR("AccessGroup"), agrp
, NULL
);
1770 SecPLLogRegisteredEvent(CFSTR("SecItem"), dict
);
1775 if (CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), CFSTR("*"))) {
1776 /* Having the special accessGroup "*" allows access to all accessGroups. */
1777 accessGroups
= NULL
;
1781 Query
*q
= query_create_with_limit(query
, client
->musr
, kSecMatchUnlimited
, error
);
1786 #if TARGET_OS_IPHONE
1787 if (q
->q_system_keychain
&& client
->inMultiUser
) {
1788 CFReleaseNull(q
->q_musrView
);
1789 q
->q_musrView
= SecMUSRCopySystemKeychainUUID();
1791 q
->q_system_keychain
= false;
1795 /* Sanity check the query. */
1796 query_set_caller_access_groups(q
, accessGroups
);
1797 if (q
->q_system_keychain
&& !client
->allowSystemKeychain
) {
1798 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for system keychain"));
1799 } else if (q
->q_sync_bubble
&& !client
->allowSyncBubbleKeychain
) {
1800 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for syncbubble keychain"));
1801 #if TARGET_OS_IPHONE
1802 } else if (q
->q_system_keychain
&& SecItemSynchronizable(attributesToUpdate
) && !client
->inMultiUser
) {
1803 ok
= SecError(errSecInvalidKey
, error
, CFSTR("Can't update an system keychain item with synchronizable"));
1805 } else if (q
->q_use_item_list
) {
1806 ok
= SecError(errSecUseItemListUnsupported
, error
, CFSTR("use item list not supported"));
1807 } else if (q
->q_return_type
& kSecReturnDataMask
) {
1808 /* Update doesn't return anything so don't ask for it. */
1809 ok
= SecError(errSecReturnDataUnsupported
, error
, CFSTR("return data not supported by update"));
1810 } else if (q
->q_return_type
& kSecReturnAttributesMask
) {
1811 ok
= SecError(errSecReturnAttributesUnsupported
, error
, CFSTR("return attributes not supported by update"));
1812 } else if (q
->q_return_type
& kSecReturnRefMask
) {
1813 ok
= SecError(errSecReturnRefUnsupported
, error
, CFSTR("return ref not supported by update"));
1814 } else if (q
->q_return_type
& kSecReturnPersistentRefMask
) {
1815 ok
= SecError(errSecReturnPersistentRefUnsupported
, error
, CFSTR("return persistent ref not supported by update"));
1817 /* Access group sanity checking. */
1818 CFStringRef agrp
= (CFStringRef
)CFDictionaryGetValue(attributesToUpdate
,
1819 kSecAttrAccessGroup
);
1821 /* The user is attempting to modify the access group column,
1822 validate it to make sure the new value is allowable. */
1823 if (CFEqual(agrp
, kSecAttrAccessGroupToken
)) {
1824 ok
= SecError(errSecParam
, error
, CFSTR("storing items into kSecAttrAccessGroupToken is not allowed"));
1826 if (!accessGroupsAllows(accessGroups
, agrp
, client
)) {
1827 ok
= SecError(errSecNoAccessForItem
, error
, CFSTR("accessGroup %@ not in %@"), agrp
, accessGroups
);
1833 ok
= kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
1834 return kc_transaction(dbt
, error
, ^{
1835 return s3dl_query_update(dbt
, q
, attributesToUpdate
, accessGroups
, error
);
1840 ok
= query_notify_and_destroy(q
, ok
, error
);
1843 SecSignpostStop(SecSignpostSecItemUpdate
);
1849 /* AUDIT[securityd](done):
1850 query (ok) is a caller provided dictionary, only its cf type has been checked.
1853 _SecItemDelete(CFDictionaryRef query
, SecurityClient
*client
, CFErrorRef
*error
)
1855 CFArrayRef accessGroups
= client
->accessGroups
;
1858 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
))) {
1859 if (SecTaskDiagnoseEntitlements
)
1860 SecTaskDiagnoseEntitlements(accessGroups
);
1861 return SecEntitlementError(errSecMissingEntitlement
, error
);
1864 SecSignpostStart(SecSignpostSecItemDelete
);
1866 if (SecPLShouldLogRegisteredEvent(CFSTR("SecItem"))) {
1867 CFTypeRef agrp
= CFArrayGetValueAtIndex(accessGroups
, 0);
1868 CFDictionaryRef dict
= CFDictionaryCreateForCFTypes(NULL
, CFSTR("operation"), CFSTR("delete"), CFSTR("AccessGroup"), agrp
, NULL
);
1870 SecPLLogRegisteredEvent(CFSTR("SecItem"), dict
);
1875 if (CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), CFSTR("*"))) {
1876 /* Having the special accessGroup "*" allows access to all accessGroups. */
1877 accessGroups
= NULL
;
1880 Query
*q
= query_create_with_limit(query
, client
->musr
, kSecMatchUnlimited
, error
);
1883 #if TARGET_OS_IPHONE
1884 if (q
->q_system_keychain
&& client
->inMultiUser
) {
1885 CFReleaseNull(q
->q_musrView
);
1886 q
->q_musrView
= SecMUSRCopySystemKeychainUUID();
1888 q
->q_system_keychain
= false;
1892 query_set_caller_access_groups(q
, accessGroups
);
1893 /* Sanity check the query. */
1894 if (q
->q_system_keychain
&& !client
->allowSystemKeychain
) {
1895 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for system keychain"));
1896 } else if (q
->q_sync_bubble
&& !client
->allowSyncBubbleKeychain
) {
1897 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for syncbubble keychain"));
1898 } else if (q
->q_limit
!= kSecMatchUnlimited
) {
1899 ok
= SecError(errSecMatchLimitUnsupported
, error
, CFSTR("match limit not supported by delete"));
1900 } else if (query_match_count(q
) != 0) {
1901 ok
= SecError(errSecItemMatchUnsupported
, error
, CFSTR("match not supported by delete"));
1902 } else if (q
->q_ref
) {
1903 ok
= SecError(errSecValueRefUnsupported
, error
, CFSTR("value ref not supported by delete"));
1904 } else if (q
->q_row_id
&& query_attr_count(q
)) {
1905 ok
= SecError(errSecItemIllegalQuery
, error
, CFSTR("rowid and other attributes are mutually exclusive"));
1906 } else if (q
->q_token_object_id
&& query_attr_count(q
) != 1) {
1907 ok
= SecError(errSecItemIllegalQuery
, error
, CFSTR("token persistent ref and other attributes are mutually exclusive"));
1909 ok
= kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
1910 return kc_transaction(dbt
, error
, ^{
1911 return s3dl_query_delete(dbt
, q
, accessGroups
, error
);
1915 ok
= query_notify_and_destroy(q
, ok
, error
);
1920 SecSignpostStop(SecSignpostSecItemDelete
);
1925 static bool SecItemDeleteTokenItems(SecDbConnectionRef dbt
, CFTypeRef classToDelete
, CFTypeRef tokenID
, CFArrayRef accessGroups
, SecurityClient
*client
, CFErrorRef
*error
) {
1926 CFTypeRef keys
[] = { kSecClass
, kSecAttrTokenID
};
1927 CFTypeRef values
[] = { classToDelete
, tokenID
};
1929 CFDictionaryRef query
= CFDictionaryCreate(kCFAllocatorDefault
, keys
, values
, 2, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1930 Query
*q
= query_create_with_limit(query
, client
->musr
, kSecMatchUnlimited
, error
);
1934 query_set_caller_access_groups(q
, accessGroups
);
1935 ok
= s3dl_query_delete(dbt
, q
, accessGroups
, error
);
1936 ok
= query_notify_and_destroy(q
, ok
, error
);
1944 static bool SecItemAddTokenItem(SecDbConnectionRef dbt
, CFDictionaryRef attributes
, CFArrayRef accessGroups
, SecurityClient
*client
, CFErrorRef
*error
) {
1946 Query
*q
= query_create_with_limit(attributes
, client
->musr
, 0, error
);
1948 CFStringRef agrp
= kSecAttrAccessGroupToken
;
1949 query_add_attribute(kSecAttrAccessGroup
, agrp
, q
);
1952 query_ensure_access_control(q
, agrp
);
1953 if (q
->q_system_keychain
&& !client
->allowSystemKeychain
) {
1954 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for system keychain"));
1955 } else if (q
->q_sync_bubble
&& !client
->allowSyncBubbleKeychain
) {
1956 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for syncbubble keychain"));
1957 } else if (q
->q_row_id
|| q
->q_token_object_id
) {
1958 ok
= SecError(errSecValuePersistentRefUnsupported
, error
, CFSTR("q_row_id")); // TODO: better error string
1959 } else if (!q
->q_error
) {
1960 query_pre_add(q
, true);
1961 ok
= s3dl_query_add(dbt
, q
, NULL
, error
);
1964 ok
= query_notify_and_destroy(q
, ok
, error
);
1971 bool _SecItemUpdateTokenItems(CFStringRef tokenID
, CFArrayRef items
, SecurityClient
*client
, CFErrorRef
*error
) {
1973 CFArrayRef accessGroups
= client
->accessGroups
;
1975 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
))) {
1976 if (SecTaskDiagnoseEntitlements
)
1977 SecTaskDiagnoseEntitlements(accessGroups
);
1978 return SecEntitlementError(errSecMissingEntitlement
, error
);
1981 ok
= kc_with_dbt(true, error
, ^bool (SecDbConnectionRef dbt
) {
1982 return kc_transaction(dbt
, error
, ^bool {
1984 const CFTypeRef classToDelete
[] = { kSecClassGenericPassword
, kSecClassInternetPassword
, kSecClassCertificate
, kSecClassKey
};
1985 for (size_t i
= 0; i
< sizeof(classToDelete
) / sizeof(classToDelete
[0]); ++i
) {
1986 SecItemDeleteTokenItems(dbt
, classToDelete
[i
], tokenID
, accessGroups
, client
, NULL
);
1989 for (CFIndex i
= 0; i
< CFArrayGetCount(items
); ++i
) {
1990 if (!SecItemAddTokenItem(dbt
, CFArrayGetValueAtIndex(items
, i
), accessGroups
, client
, error
))
1996 const CFTypeRef classToDelete
[] = { kSecClassGenericPassword
, kSecClassInternetPassword
, kSecClassCertificate
, kSecClassKey
};
1997 bool deleted
= true;
1998 for (size_t i
= 0; i
< sizeof(classToDelete
) / sizeof(classToDelete
[0]); ++i
) {
1999 if (!SecItemDeleteTokenItems(dbt
, classToDelete
[i
], tokenID
, accessGroups
, client
, error
) && error
&& CFErrorGetCode(*error
) != errSecItemNotFound
) {
2003 else if (error
&& *error
) {
2004 CFReleaseNull(*error
);
2015 /* AUDIT[securityd](done):
2016 No caller provided inputs.
2019 SecItemServerDeleteAll(CFErrorRef
*error
) {
2020 secerror("SecItemServerDeleteAll");
2021 return kc_with_dbt(true, error
, ^bool (SecDbConnectionRef dbt
) {
2022 return (kc_transaction(dbt
, error
, ^bool {
2023 return (SecDbExec(dbt
, CFSTR("DELETE from genp;"), error
) &&
2024 SecDbExec(dbt
, CFSTR("DELETE from inet;"), error
) &&
2025 SecDbExec(dbt
, CFSTR("DELETE from cert;"), error
) &&
2026 SecDbExec(dbt
, CFSTR("DELETE from keys;"), error
));
2027 }) && SecDbExec(dbt
, CFSTR("VACUUM;"), error
));
2032 _SecItemDeleteAll(CFErrorRef
*error
) {
2033 return SecItemServerDeleteAll(error
);
2037 _SecItemServerDeleteAllWithAccessGroups(CFArrayRef accessGroups
, SecurityClient
*client
, CFErrorRef
*error
)
2039 __block
bool ok
= true;
2040 static dispatch_once_t onceToken
;
2041 static CFSetRef illegalAccessGroups
= NULL
;
2043 dispatch_once(&onceToken
, ^{
2044 const CFStringRef values
[] = {
2047 CFSTR("com.apple.security.sos"),
2048 CFSTR("lockdown-identities"),
2050 illegalAccessGroups
= CFSetCreate(NULL
, (const void **)values
, sizeof(values
)/sizeof(values
[0]), &kCFTypeSetCallBacks
);
2053 static CFTypeRef qclasses
[] = {
2059 // strange construction needed for schema indirection
2060 static dispatch_once_t qclassesOnceToken
;
2061 dispatch_once(&qclassesOnceToken
, ^{
2062 qclasses
[0] = inet_class();
2063 qclasses
[1] = genp_class();
2064 qclasses
[2] = keys_class();
2065 qclasses
[3] = cert_class();
2068 require_action_quiet(isArray(accessGroups
), fail
,
2070 SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType
, sSecXPCErrorDomain
, NULL
, error
, NULL
, CFSTR("accessGroups not CFArray, got %@"), accessGroups
));
2072 // TODO: whitelist instead? look for dev IDs like 7123498YQX.com.somedev.app
2074 require_action(CFArrayGetCount(accessGroups
) != 0, fail
,
2076 SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType
, sSecXPCErrorDomain
, NULL
, error
, NULL
, CFSTR("accessGroups e empty")));
2079 // Pre-check accessGroups for prohibited values
2080 CFArrayForEach(accessGroups
, ^(const void *value
) {
2081 CFStringRef agrp
= (CFStringRef
)value
;
2083 if (!isString(agrp
)) {
2084 SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType
, sSecXPCErrorDomain
, NULL
, error
, NULL
,
2085 CFSTR("access not a string: %@"), agrp
);
2087 } else if (CFSetContainsValue(illegalAccessGroups
, agrp
)) {
2088 SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType
, sSecXPCErrorDomain
, NULL
, error
, NULL
,
2089 CFSTR("illegal access group: %@"), accessGroups
);
2095 ok
= kc_with_dbt(true, error
, ^bool(SecDbConnectionRef dbt
) {
2096 return kc_transaction(dbt
, error
, ^bool {
2097 CFErrorRef localError
= NULL
;
2101 for (n
= 0; n
< sizeof(qclasses
)/sizeof(qclasses
[0]) && ok1
; n
++) {
2104 q
= query_create(qclasses
[n
], client
->musr
, NULL
, error
);
2107 (void)s3dl_query_delete(dbt
, q
, accessGroups
, &localError
);
2109 query_destroy(q
, error
);
2110 CFReleaseNull(localError
);
2113 }) && SecDbExec(dbt
, CFSTR("VACUUM"), error
);
2122 // MARK: Shared web credentials
2124 #if TARGET_OS_IOS && !TARGET_OS_BRIDGE
2127 #define SEC_CONST_DECL(k,v) const CFStringRef k = CFSTR(v);
2129 SEC_CONST_DECL (kSecSafariAccessGroup
, "com.apple.cfnetwork");
2130 SEC_CONST_DECL (kSecSafariDefaultComment
, "default");
2131 SEC_CONST_DECL (kSecSafariPasswordsNotSaved
, "Passwords not saved");
2132 SEC_CONST_DECL (kSecSharedCredentialUrlScheme
, "https://");
2133 SEC_CONST_DECL (kSecSharedWebCredentialsService
, "webcredentials");
2135 #if !TARGET_IPHONE_SIMULATOR
2137 _SecAppDomainApprovalStatus(CFStringRef appID
, CFStringRef fqdn
, CFErrorRef
*error
)
2139 __block SWCFlags flags
= kSWCFlags_None
;
2142 dispatch_semaphore_t semaphore
= dispatch_semaphore_create(0);
2143 if (semaphore
== NULL
)
2146 status
= SWCCheckService(kSecSharedWebCredentialsService
, appID
, fqdn
, ^void (OSStatus inStatus
, SWCFlags inFlags
, CFDictionaryRef inDetails
)
2148 if (inStatus
== 0) {
2151 secerror("SWCCheckService failed with %d", (int)inStatus
);
2153 dispatch_semaphore_signal(semaphore
);
2157 dispatch_semaphore_wait(semaphore
, DISPATCH_TIME_FOREVER
);
2159 secerror("SWCCheckService: failed to queue");
2161 dispatch_release(semaphore
);
2164 if (!(flags
& kSWCFlag_SiteApproved
)) {
2165 if (flags
& kSWCFlag_Pending
) {
2166 SecError(errSecAuthFailed
, error
, CFSTR("Approval is pending for \"%@\", try later"), fqdn
);
2168 SecError(errSecAuthFailed
, error
, CFSTR("\"%@\" failed to approve \"%@\""), fqdn
, appID
);
2170 } else if (flags
& kSWCFlag_UserDenied
) {
2171 SecError(errSecAuthFailed
, error
, CFSTR("User denied access to \"%@\" by \"%@\""), fqdn
, appID
);
2178 _SecEntitlementContainsDomainForService(CFArrayRef domains
, CFStringRef domain
, CFStringRef service
)
2180 bool result
= false;
2181 CFIndex idx
, count
= (domains
) ? CFArrayGetCount(domains
) : (CFIndex
) 0;
2182 if (!count
|| !domain
|| !service
) {
2185 for (idx
=0; idx
< count
; idx
++) {
2186 CFStringRef str
= (CFStringRef
) CFArrayGetValueAtIndex(domains
, idx
);
2187 if (str
&& CFStringHasPrefix(str
, kSecSharedWebCredentialsService
)) {
2188 CFIndex prefix_len
= CFStringGetLength(kSecSharedWebCredentialsService
)+1;
2189 CFIndex substr_len
= CFStringGetLength(str
) - prefix_len
;
2190 CFRange range
= { prefix_len
, substr_len
};
2191 CFStringRef substr
= CFStringCreateWithSubstring(kCFAllocatorDefault
, str
, range
);
2192 if (substr
&& CFEqual(substr
, domain
)) {
2195 CFReleaseSafe(substr
);
2203 #endif /* !TARGET_OS_SIMULATOR */
2206 _SecAddNegativeWebCredential(SecurityClient
*client
, CFStringRef fqdn
, CFStringRef appID
, bool forSafari
)
2208 #if !TARGET_IPHONE_SIMULATOR
2209 bool result
= false;
2210 if (!fqdn
) { return result
; }
2212 // update our database
2213 CFRetainSafe(appID
);
2215 if (0 == SWCSetServiceFlags(kSecSharedWebCredentialsService
, appID
, fqdn
, kSWCFlag_ExternalMask
, kSWCFlag_UserDenied
,
2216 ^void(OSStatus inStatus
, SWCFlags inNewFlags
){
2217 CFReleaseSafe(appID
);
2218 CFReleaseSafe(fqdn
);
2223 else // didn't queue the block
2225 CFReleaseSafe(appID
);
2226 CFReleaseSafe(fqdn
);
2229 if (!forSafari
) { return result
; }
2231 // below this point: create a negative Safari web credential item
2233 CFMutableDictionaryRef attrs
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2234 if (!attrs
) { return result
; }
2236 CFErrorRef error
= NULL
;
2237 CFStringRef accessGroup
= CFSTR("*");
2238 SecurityClient swcclient
= {
2240 .accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&accessGroup
, 1, &kCFTypeArrayCallBacks
),
2241 .allowSystemKeychain
= false,
2242 .allowSyncBubbleKeychain
= false,
2243 .isNetworkExtension
= false,
2244 .musr
= client
->musr
,
2247 CFDictionaryAddValue(attrs
, kSecClass
, kSecClassInternetPassword
);
2248 CFDictionaryAddValue(attrs
, kSecAttrAccessGroup
, kSecSafariAccessGroup
);
2249 CFDictionaryAddValue(attrs
, kSecAttrAuthenticationType
, kSecAttrAuthenticationTypeHTMLForm
);
2250 CFDictionaryAddValue(attrs
, kSecAttrProtocol
, kSecAttrProtocolHTTPS
);
2251 CFDictionaryAddValue(attrs
, kSecAttrServer
, fqdn
);
2252 CFDictionaryAddValue(attrs
, kSecAttrSynchronizable
, kCFBooleanTrue
);
2254 (void)_SecItemDelete(attrs
, &swcclient
, &error
);
2255 CFReleaseNull(error
);
2257 CFDictionaryAddValue(attrs
, kSecAttrAccount
, kSecSafariPasswordsNotSaved
);
2258 CFDictionaryAddValue(attrs
, kSecAttrComment
, kSecSafariDefaultComment
);
2260 CFStringRef label
= CFStringCreateWithFormat(kCFAllocatorDefault
,
2261 NULL
, CFSTR("%@ (%@)"), fqdn
, kSecSafariPasswordsNotSaved
);
2263 CFDictionaryAddValue(attrs
, kSecAttrLabel
, label
);
2264 CFReleaseSafe(label
);
2268 CFDataRef data
= CFDataCreate(kCFAllocatorDefault
, &space
, 1);
2270 CFDictionarySetValue(attrs
, kSecValueData
, data
);
2271 CFReleaseSafe(data
);
2274 CFTypeRef addResult
= NULL
;
2275 result
= _SecItemAdd(attrs
, &swcclient
, &addResult
, &error
);
2277 CFReleaseSafe(addResult
);
2278 CFReleaseSafe(error
);
2279 CFReleaseSafe(attrs
);
2280 CFReleaseSafe(swcclient
.accessGroups
);
2288 /* Specialized version of SecItemAdd for shared web credentials */
2290 _SecAddSharedWebCredential(CFDictionaryRef attributes
,
2291 SecurityClient
*client
,
2292 const audit_token_t
*clientAuditToken
,
2299 SecurityClient swcclient
= {};
2301 CFStringRef fqdn
= CFRetainSafe(CFDictionaryGetValue(attributes
, kSecAttrServer
));
2302 CFStringRef account
= CFDictionaryGetValue(attributes
, kSecAttrAccount
);
2303 #if TARGET_OS_IOS && !TARGET_OS_BRIDGE
2304 CFStringRef password
= CFDictionaryGetValue(attributes
, kSecSharedPassword
);
2306 CFStringRef password
= CFDictionaryGetValue(attributes
, CFSTR("spwd"));
2308 CFStringRef accessGroup
= CFSTR("*");
2309 CFMutableDictionaryRef query
= NULL
, attrs
= NULL
;
2313 // check autofill enabled status
2314 if (!swca_autofill_enabled(clientAuditToken
)) {
2315 SecError(errSecBadReq
, error
, CFSTR("Autofill is not enabled in Safari settings"));
2319 // parse fqdn with CFURL here, since it could be specified as domain:port
2321 CFStringRef urlStr
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%@%@"), kSecSharedCredentialUrlScheme
, fqdn
);
2323 CFURLRef url
= CFURLCreateWithString(kCFAllocatorDefault
, urlStr
, nil
);
2325 CFStringRef hostname
= CFURLCopyHostName(url
);
2327 CFReleaseSafe(fqdn
);
2329 port
= CFURLGetPortNumber(url
);
2333 CFReleaseSafe(urlStr
);
2338 SecError(errSecParam
, error
, CFSTR("No account provided"));
2342 SecError(errSecParam
, error
, CFSTR("No domain provided"));
2346 #if TARGET_IPHONE_SIMULATOR
2347 secerror("app/site association entitlements not checked in Simulator");
2349 OSStatus status
= errSecMissingEntitlement
;
2350 // validate that fqdn is part of caller's shared credential domains entitlement
2352 SecError(status
, error
, CFSTR("Missing application-identifier entitlement"));
2355 if (_SecEntitlementContainsDomainForService(domains
, fqdn
, kSecSharedWebCredentialsService
)) {
2356 status
= errSecSuccess
;
2358 if (errSecSuccess
!= status
) {
2359 CFStringRef msg
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
,
2360 CFSTR("%@ not found in %@ entitlement"), fqdn
, kSecEntitlementAssociatedDomains
);
2362 msg
= CFRetain(CFSTR("Requested domain not found in entitlement"));
2364 SecError(status
, error
, CFSTR("%@"), msg
);
2370 #if TARGET_IPHONE_SIMULATOR
2371 secerror("Ignoring app/site approval state in the Simulator.");
2373 // get approval status for this app/domain pair
2374 SWCFlags flags
= _SecAppDomainApprovalStatus(appID
, fqdn
, error
);
2375 if (!(flags
& kSWCFlag_SiteApproved
)) {
2380 // give ourselves access to see matching items for kSecSafariAccessGroup
2381 swcclient
.task
= NULL
;
2382 swcclient
.accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&accessGroup
, 1, &kCFTypeArrayCallBacks
);
2383 swcclient
.allowSystemKeychain
= false;
2384 swcclient
.musr
= client
->musr
;
2385 swcclient
.allowSystemKeychain
= false;
2386 swcclient
.allowSyncBubbleKeychain
= false;
2387 swcclient
.isNetworkExtension
= false;
2390 // create lookup query
2391 query
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2393 SecError(errSecAllocate
, error
, CFSTR("Unable to create query dictionary"));
2396 CFDictionaryAddValue(query
, kSecClass
, kSecClassInternetPassword
);
2397 CFDictionaryAddValue(query
, kSecAttrAccessGroup
, kSecSafariAccessGroup
);
2398 CFDictionaryAddValue(query
, kSecAttrAuthenticationType
, kSecAttrAuthenticationTypeHTMLForm
);
2399 CFDictionaryAddValue(query
, kSecAttrServer
, fqdn
);
2400 CFDictionaryAddValue(query
, kSecAttrSynchronizable
, kCFBooleanTrue
);
2402 // check for presence of Safari's negative entry ('passwords not saved')
2403 CFDictionarySetValue(query
, kSecAttrAccount
, kSecSafariPasswordsNotSaved
);
2404 ok
= _SecItemCopyMatching(query
, &swcclient
, result
, error
);
2405 if(result
) CFReleaseNull(*result
);
2406 if (error
) CFReleaseNull(*error
);
2408 SecError(errSecDuplicateItem
, error
, CFSTR("Item already exists for this server"));
2412 // now use the provided account (and optional port number, if one was present)
2413 CFDictionarySetValue(query
, kSecAttrAccount
, account
);
2414 if (port
< -1 || port
> 0) {
2415 SInt16 portValueShort
= (port
& 0xFFFF);
2416 CFNumberRef portNumber
= CFNumberCreate(NULL
, kCFNumberSInt16Type
, &portValueShort
);
2417 CFDictionaryAddValue(query
, kSecAttrPort
, portNumber
);
2418 CFReleaseSafe(portNumber
);
2421 // look up existing password
2422 CFDictionaryAddValue(query
, kSecReturnData
, kCFBooleanTrue
);
2423 bool matched
= _SecItemCopyMatching(query
, &swcclient
, result
, error
);
2424 CFDictionaryRemoveValue(query
, kSecReturnData
);
2426 // found it, so this becomes either an "update password" or "delete password" operation
2427 bool update
= (password
!= NULL
);
2429 attrs
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2430 CFDataRef credential
= CFStringCreateExternalRepresentation(kCFAllocatorDefault
, password
, kCFStringEncodingUTF8
, 0);
2431 CFDictionaryAddValue(attrs
, kSecValueData
, credential
);
2432 bool samePassword
= result
&& *result
&& CFEqual(*result
, credential
);
2433 CFReleaseSafe(credential
);
2434 CFDictionaryAddValue(attrs
, kSecAttrComment
, kSecSafariDefaultComment
);
2436 ok
= samePassword
|| swca_confirm_operation(swca_update_request_id
, clientAuditToken
, query
, error
,
2437 ^void (CFStringRef fqdn
) { _SecAddNegativeWebCredential(client
, fqdn
, appID
, false); });
2439 ok
= _SecItemUpdate(query
, attrs
, &swcclient
, error
);
2443 // confirm the delete
2444 // (per rdar://16676288 we always prompt, even if there was prior user approval)
2445 ok
= /*approved ||*/ swca_confirm_operation(swca_delete_request_id
, clientAuditToken
, query
, error
,
2446 ^void (CFStringRef fqdn
) { _SecAddNegativeWebCredential(client
, fqdn
, appID
, false); });
2448 ok
= _SecItemDelete(query
, &swcclient
, error
);
2452 if(result
) CFReleaseNull(*result
);
2453 if(error
) CFReleaseNull(*error
);
2457 if (result
) CFReleaseNull(*result
);
2458 if (error
) CFReleaseNull(*error
);
2460 // password does not exist, so prepare to add it
2462 // a NULL password value removes the existing credential. Since we didn't find it, this is a no-op.
2467 CFStringRef label
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%@ (%@)"), fqdn
, account
);
2469 CFDictionaryAddValue(query
, kSecAttrLabel
, label
);
2470 CFReleaseSafe(label
);
2472 // NOTE: we always expect to use HTTPS for web forms.
2473 CFDictionaryAddValue(query
, kSecAttrProtocol
, kSecAttrProtocolHTTPS
);
2475 CFDataRef credential
= CFStringCreateExternalRepresentation(kCFAllocatorDefault
, password
, kCFStringEncodingUTF8
, 0);
2476 CFDictionarySetValue(query
, kSecValueData
, credential
);
2477 CFReleaseSafe(credential
);
2478 CFDictionarySetValue(query
, kSecAttrComment
, kSecSafariDefaultComment
);
2480 CFReleaseSafe(swcclient
.accessGroups
);
2481 swcclient
.accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&kSecSafariAccessGroup
, 1, &kCFTypeArrayCallBacks
);
2483 // mark the item as created by this function
2484 const int32_t creator_value
= 'swca';
2485 CFNumberRef creator
= CFNumberCreate(kCFAllocatorDefault
, kCFNumberSInt32Type
, &creator_value
);
2487 CFDictionarySetValue(query
, kSecAttrCreator
, creator
);
2488 CFReleaseSafe(creator
);
2493 // (per rdar://16680019, we won't prompt here in the normal case)
2494 ok
= /*approved ||*/ swca_confirm_operation(swca_add_request_id
, clientAuditToken
, query
, error
,
2495 ^void (CFStringRef fqdn
) { _SecAddNegativeWebCredential(client
, fqdn
, appID
, false); });
2499 ok
= _SecItemAdd(query
, &swcclient
, result
, error
);
2503 CFReleaseSafe(attrs
);
2504 CFReleaseSafe(query
);
2505 CFReleaseSafe(swcclient
.accessGroups
);
2506 CFReleaseSafe(fqdn
);
2510 /* Specialized version of SecItemCopyMatching for shared web credentials */
2512 _SecCopySharedWebCredential(CFDictionaryRef query
,
2513 SecurityClient
*client
,
2514 const audit_token_t
*clientAuditToken
,
2520 CFMutableArrayRef credentials
= NULL
;
2521 CFMutableArrayRef foundItems
= NULL
;
2522 CFMutableArrayRef fqdns
= NULL
;
2523 CFStringRef fqdn
= NULL
;
2524 CFStringRef account
= NULL
;
2529 require_quiet(result
, cleanup
);
2530 credentials
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
2531 foundItems
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
2532 fqdns
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
2534 // give ourselves access to see matching items for kSecSafariAccessGroup
2535 CFStringRef accessGroup
= CFSTR("*");
2536 SecurityClient swcclient
= {
2538 .accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&accessGroup
, 1, &kCFTypeArrayCallBacks
),
2539 .allowSystemKeychain
= false,
2540 .allowSyncBubbleKeychain
= false,
2541 .isNetworkExtension
= false,
2542 .musr
= client
->musr
,
2545 // On input, the query dictionary contains optional fqdn and account entries.
2546 fqdn
= CFDictionaryGetValue(query
, kSecAttrServer
);
2547 account
= CFDictionaryGetValue(query
, kSecAttrAccount
);
2549 // Check autofill enabled status
2550 if (!swca_autofill_enabled(clientAuditToken
)) {
2551 SecError(errSecBadReq
, error
, CFSTR("Autofill is not enabled in Safari settings"));
2555 // Check fqdn; if NULL, add domains from caller's entitlement.
2557 CFArrayAppendValue(fqdns
, fqdn
);
2560 CFIndex idx
, count
= CFArrayGetCount(domains
);
2561 for (idx
=0; idx
< count
; idx
++) {
2562 CFStringRef str
= (CFStringRef
) CFArrayGetValueAtIndex(domains
, idx
);
2563 // Parse the entry for our service label prefix
2564 if (str
&& CFStringHasPrefix(str
, kSecSharedWebCredentialsService
)) {
2565 CFIndex prefix_len
= CFStringGetLength(kSecSharedWebCredentialsService
)+1;
2566 CFIndex substr_len
= CFStringGetLength(str
) - prefix_len
;
2567 CFRange range
= { prefix_len
, substr_len
};
2568 fqdn
= CFStringCreateWithSubstring(kCFAllocatorDefault
, str
, range
);
2570 CFArrayAppendValue(fqdns
, fqdn
);
2576 count
= CFArrayGetCount(fqdns
);
2578 SecError(errSecParam
, error
, CFSTR("No domain provided"));
2582 // Aggregate search results for each domain
2583 for (idx
= 0; idx
< count
; idx
++) {
2584 CFMutableArrayRef items
= NULL
;
2585 CFMutableDictionaryRef attrs
= NULL
;
2586 fqdn
= (CFStringRef
) CFArrayGetValueAtIndex(fqdns
, idx
);
2590 // Parse the fqdn for a possible port specifier.
2592 CFStringRef urlStr
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%@%@"), kSecSharedCredentialUrlScheme
, fqdn
);
2594 CFURLRef url
= CFURLCreateWithString(kCFAllocatorDefault
, urlStr
, nil
);
2596 CFStringRef hostname
= CFURLCopyHostName(url
);
2598 CFReleaseSafe(fqdn
);
2600 port
= CFURLGetPortNumber(url
);
2604 CFReleaseSafe(urlStr
);
2608 #if TARGET_IPHONE_SIMULATOR
2609 secerror("app/site association entitlements not checked in Simulator");
2611 OSStatus status
= errSecMissingEntitlement
;
2613 SecError(status
, error
, CFSTR("Missing application-identifier entitlement"));
2614 CFReleaseSafe(fqdn
);
2617 // validate that fqdn is part of caller's entitlement
2618 if (_SecEntitlementContainsDomainForService(domains
, fqdn
, kSecSharedWebCredentialsService
)) {
2619 status
= errSecSuccess
;
2621 if (errSecSuccess
!= status
) {
2622 CFStringRef msg
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
,
2623 CFSTR("%@ not found in %@ entitlement"), fqdn
, kSecEntitlementAssociatedDomains
);
2625 msg
= CFRetain(CFSTR("Requested domain not found in entitlement"));
2627 SecError(status
, error
, CFSTR("%@"), msg
);
2629 CFReleaseSafe(fqdn
);
2634 attrs
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2636 SecError(errSecAllocate
, error
, CFSTR("Unable to create query dictionary"));
2637 CFReleaseSafe(fqdn
);
2640 CFDictionaryAddValue(attrs
, kSecClass
, kSecClassInternetPassword
);
2641 CFDictionaryAddValue(attrs
, kSecAttrAccessGroup
, kSecSafariAccessGroup
);
2642 CFDictionaryAddValue(attrs
, kSecAttrProtocol
, kSecAttrProtocolHTTPS
);
2643 CFDictionaryAddValue(attrs
, kSecAttrAuthenticationType
, kSecAttrAuthenticationTypeHTMLForm
);
2644 CFDictionaryAddValue(attrs
, kSecAttrServer
, fqdn
);
2646 CFDictionaryAddValue(attrs
, kSecAttrAccount
, account
);
2648 if (port
< -1 || port
> 0) {
2649 SInt16 portValueShort
= (port
& 0xFFFF);
2650 CFNumberRef portNumber
= CFNumberCreate(NULL
, kCFNumberSInt16Type
, &portValueShort
);
2651 CFDictionaryAddValue(attrs
, kSecAttrPort
, portNumber
);
2652 CFReleaseSafe(portNumber
);
2654 CFDictionaryAddValue(attrs
, kSecAttrSynchronizable
, kCFBooleanTrue
);
2655 CFDictionaryAddValue(attrs
, kSecMatchLimit
, kSecMatchLimitAll
);
2656 CFDictionaryAddValue(attrs
, kSecReturnAttributes
, kCFBooleanTrue
);
2657 CFDictionaryAddValue(attrs
, kSecReturnData
, kCFBooleanTrue
);
2659 ok
= _SecItemCopyMatching(attrs
, &swcclient
, (CFTypeRef
*)&items
, error
);
2661 // ignore interim error since we have multiple domains to search
2662 CFReleaseNull(*error
);
2664 if (ok
&& items
&& CFGetTypeID(items
) == CFArrayGetTypeID()) {
2665 #if TARGET_IPHONE_SIMULATOR
2666 secerror("Ignoring app/site approval state in the Simulator.");
2667 bool approved
= true;
2669 // get approval status for this app/domain pair
2670 SWCFlags flags
= _SecAppDomainApprovalStatus(appID
, fqdn
, error
);
2672 // ignore interim error since we have multiple domains to check
2673 CFReleaseNull(*error
);
2675 bool approved
= (flags
& kSWCFlag_SiteApproved
);
2678 CFArrayAppendArray(foundItems
, items
, CFRangeMake(0, CFArrayGetCount(items
)));
2681 CFReleaseSafe(items
);
2682 CFReleaseSafe(attrs
);
2683 CFReleaseSafe(fqdn
);
2686 // If matching credentials are found, the credentials provided to the completionHandler
2687 // will be a CFArrayRef containing CFDictionaryRef entries. Each dictionary entry will
2688 // contain the following pairs (see Security/SecItem.h):
2689 // key: kSecAttrServer value: CFStringRef (the website)
2690 // key: kSecAttrAccount value: CFStringRef (the account)
2691 // key: kSecSharedPassword value: CFStringRef (the password)
2693 // key: kSecAttrPort value: CFNumberRef (the port number, if non-standard for https)
2695 count
= CFArrayGetCount(foundItems
);
2696 for (idx
= 0; idx
< count
; idx
++) {
2697 CFDictionaryRef dict
= (CFDictionaryRef
) CFArrayGetValueAtIndex(foundItems
, idx
);
2698 CFMutableDictionaryRef newdict
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2699 if (newdict
&& dict
&& CFGetTypeID(dict
) == CFDictionaryGetTypeID()) {
2700 CFStringRef srvr
= CFDictionaryGetValue(dict
, kSecAttrServer
);
2701 CFStringRef acct
= CFDictionaryGetValue(dict
, kSecAttrAccount
);
2702 CFNumberRef pnum
= CFDictionaryGetValue(dict
, kSecAttrPort
);
2703 CFStringRef icmt
= CFDictionaryGetValue(dict
, kSecAttrComment
);
2704 CFDataRef data
= CFDictionaryGetValue(dict
, kSecValueData
);
2706 CFDictionaryAddValue(newdict
, kSecAttrServer
, srvr
);
2709 CFDictionaryAddValue(newdict
, kSecAttrAccount
, acct
);
2713 if (CFNumberGetValue(pnum
, kCFNumberSInt16Type
, &pval
) &&
2714 (pval
< -1 || pval
> 0)) {
2715 CFDictionaryAddValue(newdict
, kSecAttrPort
, pnum
);
2719 CFStringRef password
= CFStringCreateFromExternalRepresentation(kCFAllocatorDefault
, data
, kCFStringEncodingUTF8
);
2721 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH && !TARGET_OS_TV
2722 CFDictionaryAddValue(newdict
, kSecSharedPassword
, password
);
2724 CFDictionaryAddValue(newdict
, CFSTR("spwd"), password
);
2726 CFReleaseSafe(password
);
2729 if (icmt
&& CFEqual(icmt
, kSecSafariDefaultComment
)) {
2730 CFArrayInsertValueAtIndex(credentials
, 0, newdict
);
2732 CFArrayAppendValue(credentials
, newdict
);
2735 CFReleaseSafe(newdict
);
2742 // create a new array of dictionaries (without the actual password) for picker UI
2743 count
= CFArrayGetCount(credentials
);
2744 CFMutableArrayRef items
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
2745 for (idx
= 0; idx
< count
; idx
++) {
2746 CFDictionaryRef dict
= (CFDictionaryRef
) CFArrayGetValueAtIndex(credentials
, idx
);
2747 CFMutableDictionaryRef newdict
= CFDictionaryCreateMutableCopy(kCFAllocatorDefault
, 0, dict
);
2748 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH && !TARGET_OS_TV
2749 CFDictionaryRemoveValue(newdict
, kSecSharedPassword
);
2751 CFDictionaryRemoveValue(newdict
, CFSTR("spwd"));
2753 CFArrayAppendValue(items
, newdict
);
2754 CFReleaseSafe(newdict
);
2757 // prompt user to select one of the dictionary items
2758 CFDictionaryRef selected
= swca_copy_selected_dictionary(swca_select_request_id
,
2759 clientAuditToken
, items
, error
);
2761 // find the matching item in our credentials array
2762 CFStringRef srvr
= CFDictionaryGetValue(selected
, kSecAttrServer
);
2763 CFStringRef acct
= CFDictionaryGetValue(selected
, kSecAttrAccount
);
2764 CFNumberRef pnum
= CFDictionaryGetValue(selected
, kSecAttrPort
);
2765 for (idx
= 0; idx
< count
; idx
++) {
2766 CFDictionaryRef dict
= (CFDictionaryRef
) CFArrayGetValueAtIndex(credentials
, idx
);
2767 CFStringRef srvr1
= CFDictionaryGetValue(dict
, kSecAttrServer
);
2768 CFStringRef acct1
= CFDictionaryGetValue(dict
, kSecAttrAccount
);
2769 CFNumberRef pnum1
= CFDictionaryGetValue(dict
, kSecAttrPort
);
2771 if (!srvr
|| !srvr1
|| !CFEqual(srvr
, srvr1
)) continue;
2772 if (!acct
|| !acct1
|| !CFEqual(acct
, acct1
)) continue;
2773 if ((pnum
&& pnum1
) && !CFEqual(pnum
, pnum1
)) continue;
2776 CFReleaseSafe(selected
);
2783 CFReleaseSafe(items
);
2784 CFArrayRemoveAllValues(credentials
);
2785 if (selected
&& ok
) {
2786 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
2787 fqdn
= CFDictionaryGetValue(selected
, kSecAttrServer
);
2789 CFArrayAppendValue(credentials
, selected
);
2793 #if TARGET_OS_IOS && !TARGET_OS_BRIDGE && !TARGET_IPHONE_SIMULATOR
2794 // register confirmation with database
2795 CFRetainSafe(appID
);
2797 if (0 != SWCSetServiceFlags(kSecSharedWebCredentialsService
,
2798 appID
, fqdn
, kSWCFlag_ExternalMask
, kSWCFlag_UserApproved
,
2799 ^void(OSStatus inStatus
, SWCFlags inNewFlags
){
2800 CFReleaseSafe(appID
);
2801 CFReleaseSafe(fqdn
);
2804 // we didn't queue the block
2805 CFReleaseSafe(appID
);
2806 CFReleaseSafe(fqdn
);
2810 CFReleaseSafe(selected
);
2812 else if (NULL
== *error
) {
2813 // found no items, and we haven't already filled in the error
2814 SecError(errSecItemNotFound
, error
, CFSTR("no matching items found"));
2819 CFArrayRemoveAllValues(credentials
);
2820 CFReleaseNull(credentials
);
2822 CFReleaseSafe(foundItems
);
2823 *result
= credentials
;
2824 CFReleaseSafe(swcclient
.accessGroups
);
2825 CFReleaseSafe(fqdns
);
2830 #endif /* TARGET_OS_IOS */
2834 // MARK: Keychain backup
2836 CF_RETURNS_RETAINED CFDataRef
2837 _SecServerKeychainCreateBackup(SecurityClient
*client
, CFDataRef keybag
, CFDataRef passcode
, bool emcs
, CFErrorRef
*error
) {
2838 __block CFDataRef backup
;
2839 kc_with_dbt(true, error
, ^bool (SecDbConnectionRef dbt
) {
2843 if (keybag
== NULL
&& passcode
== NULL
) {
2845 backup
= SecServerExportBackupableKeychain(dbt
, client
, KEYBAG_DEVICE
, backup_keybag_handle
, error
);
2846 #else /* !USE_KEYSTORE */
2848 SecError(errSecParam
, error
, CFSTR("Why are you doing this?"));
2850 #endif /* USE_KEYSTORE */
2852 backup
= SecServerKeychainCreateBackup(dbt
, client
, keybag
, passcode
, emcs
, error
);
2854 return (backup
!= NULL
);
2861 _SecServerKeychainRestore(CFDataRef backup
, SecurityClient
*client
, CFDataRef keybag
, CFDataRef passcode
, CFErrorRef
*error
) {
2862 if (backup
== NULL
|| keybag
== NULL
)
2863 return SecError(errSecParam
, error
, CFSTR("backup or keybag missing"));
2865 __block
bool ok
= true;
2866 ok
&= kc_with_dbt(true, error
, ^bool (SecDbConnectionRef dbconn
) {
2867 return SecServerKeychainRestore(dbconn
, client
, backup
, keybag
, passcode
, error
);
2871 SecKeychainChanged();
2878 _SecServerBackupCopyUUID(CFDataRef data
, CFErrorRef
*error
)
2880 CFStringRef uuid
= NULL
;
2881 CFDictionaryRef backup
;
2883 backup
= CFPropertyListCreateWithData(kCFAllocatorDefault
, data
,
2884 kCFPropertyListImmutable
, NULL
,
2886 if (isDictionary(backup
)) {
2887 uuid
= SecServerBackupGetKeybagUUID(backup
, error
);
2891 CFReleaseNull(backup
);
2899 // MARK: SecItemDataSource
2901 // Make sure to call this before any writes to the keychain, so that we fire
2902 // up the engines to monitor manifest changes.
2903 SOSDataSourceFactoryRef
SecItemDataSourceFactoryGetDefault(void) {
2904 return SecItemDataSourceFactoryGetShared(kc_dbhandle(NULL
));
2907 /* AUDIT[securityd]:
2908 args_in (ok) is a caller provided, CFDictionaryRef.
2911 CF_RETURNS_RETAINED CFArrayRef
2912 _SecServerKeychainSyncUpdateMessage(CFDictionaryRef updates
, CFErrorRef
*error
) {
2913 // This never fails, trust us!
2914 return SOSCCHandleUpdateMessage(updates
);
2918 // Truthiness in the cloud backup/restore support.
2921 static CFDictionaryRef
2922 _SecServerCopyTruthInTheCloud(CFDataRef keybag
, CFDataRef password
,
2923 CFDictionaryRef backup
, CFErrorRef
*error
)
2925 SOSManifestRef mold
= NULL
, mnow
= NULL
, mdelete
= NULL
, madd
= NULL
;
2926 __block CFMutableDictionaryRef backup_new
= NULL
;
2927 keybag_handle_t bag_handle
;
2928 if (!ks_open_keybag(keybag
, password
, &bag_handle
, error
))
2931 // We need to have a datasource singleton for protection domain
2932 // kSecAttrAccessibleWhenUnlocked and keep a single shared engine
2933 // instance around which we create in the datasource constructor as well.
2934 SOSDataSourceFactoryRef dsf
= SecItemDataSourceFactoryGetDefault();
2935 SOSDataSourceRef ds
= SOSDataSourceFactoryCreateDataSource(dsf
, kSecAttrAccessibleWhenUnlocked
, error
);
2937 backup_new
= backup
? CFDictionaryCreateMutableCopy(kCFAllocatorDefault
, 0, backup
) : CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2938 mold
= SOSCreateManifestWithBackup(backup
, error
);
2939 SOSEngineRef engine
= SOSDataSourceGetSharedEngine(ds
, error
);
2940 mnow
= SOSEngineCopyManifest(engine
, NULL
);
2942 mnow
= SOSDataSourceCopyManifestWithViewNameSet(ds
, SOSViewsGetV0ViewSet(), error
);
2945 CFReleaseNull(backup_new
);
2946 secerror("failed to obtain manifest for keychain: %@", error
? *error
: NULL
);
2948 SOSManifestDiff(mold
, mnow
, &mdelete
, &madd
, error
);
2951 // Delete everything from the new_backup that is no longer in the datasource according to the datasources manifest.
2952 SOSManifestForEach(mdelete
, ^(CFDataRef digest_data
, bool *stop
) {
2953 CFStringRef deleted_item_key
= CFDataCopyHexString(digest_data
);
2954 CFDictionaryRemoveValue(backup_new
, deleted_item_key
);
2955 CFRelease(deleted_item_key
);
2958 CFMutableArrayRef changes
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
2959 SOSDataSourceForEachObject(ds
, NULL
, madd
, error
, ^void(CFDataRef digest
, SOSObjectRef object
, bool *stop
) {
2960 CFErrorRef localError
= NULL
;
2961 CFDataRef digest_data
= NULL
;
2962 CFTypeRef value
= NULL
;
2964 // Key in our manifest can't be found in db, remove it from our manifest
2965 SOSChangesAppendDelete(changes
, digest
);
2966 } else if (!(digest_data
= SOSObjectCopyDigest(ds
, object
, &localError
))
2967 || !(value
= SOSObjectCopyBackup(ds
, object
, bag_handle
, &localError
))) {
2968 if (SecErrorGetOSStatus(localError
) == errSecDecode
) {
2969 // Ignore decode errors, pretend the objects aren't there
2970 CFRelease(localError
);
2971 // Object undecodable, remove it from our manifest
2972 SOSChangesAppendDelete(changes
, digest
);
2974 // Stop iterating and propagate out all other errors.
2976 *error
= localError
;
2977 CFReleaseNull(backup_new
);
2980 // TODO: Should we skip tombstones here?
2981 CFStringRef key
= CFDataCopyHexString(digest_data
);
2982 CFDictionarySetValue(backup_new
, key
, value
);
2985 CFReleaseSafe(digest_data
);
2986 CFReleaseSafe(value
);
2987 }) || CFReleaseNull(backup_new
);
2989 if (CFArrayGetCount(changes
)) {
2990 if (!SOSEngineUpdateChanges(engine
, kSOSDataSourceSOSTransaction
, changes
, error
)) {
2991 CFReleaseNull(backup_new
);
2994 CFReleaseSafe(changes
);
2996 SOSDataSourceRelease(ds
, error
) || CFReleaseNull(backup_new
);
2999 CFReleaseSafe(mold
);
3000 CFReleaseSafe(mnow
);
3001 CFReleaseSafe(madd
);
3002 CFReleaseSafe(mdelete
);
3003 ks_close_keybag(bag_handle
, error
) || CFReleaseNull(backup_new
);
3009 _SecServerRestoreTruthInTheCloud(CFDataRef keybag
, CFDataRef password
, CFDictionaryRef backup_in
, CFErrorRef
*error
) {
3010 __block
bool ok
= true;
3011 keybag_handle_t bag_handle
;
3012 if (!ks_open_keybag(keybag
, password
, &bag_handle
, error
))
3015 SOSManifestRef mbackup
= SOSCreateManifestWithBackup(backup_in
, error
);
3017 SOSDataSourceFactoryRef dsf
= SecItemDataSourceFactoryGetDefault();
3018 SOSDataSourceRef ds
= SOSDataSourceFactoryCreateDataSource(dsf
, kSecAttrAccessibleWhenUnlocked
, error
);
3019 ok
&= ds
&& SOSDataSourceWith(ds
, error
, ^(SOSTransactionRef txn
, bool *commit
) {
3020 SOSManifestRef mnow
= SOSDataSourceCopyManifestWithViewNameSet(ds
, SOSViewsGetV0BackupViewSet(), error
);
3021 SOSManifestRef mdelete
= NULL
, madd
= NULL
;
3022 SOSManifestDiff(mnow
, mbackup
, &mdelete
, &madd
, error
);
3024 // Don't delete everything in datasource not in backup.
3026 // Add items from the backup
3027 SOSManifestForEach(madd
, ^void(CFDataRef e
, bool *stop
) {
3028 CFDictionaryRef item
= NULL
;
3029 CFStringRef sha1
= CFDataCopyHexString(e
);
3031 item
= CFDictionaryGetValue(backup_in
, sha1
);
3035 CFErrorRef localError
= NULL
;
3037 if (!SOSObjectRestoreObject(ds
, txn
, bag_handle
, item
, &localError
)) {
3038 OSStatus status
= SecErrorGetOSStatus(localError
);
3039 if (status
== errSecDuplicateItem
) {
3040 // Log and ignore duplicate item errors during restore
3041 secnotice("titc", "restore %@ not replacing existing item", item
);
3042 } else if (status
== errSecDecode
) {
3043 // Log and ignore corrupted item errors during restore
3044 secnotice("titc", "restore %@ skipping corrupted item %@", item
, localError
);
3046 if (status
== errSecInteractionNotAllowed
)
3048 // Propagate the first other error upwards (causing the restore to fail).
3049 secerror("restore %@ failed %@", item
, localError
);
3051 if (error
&& !*error
) {
3052 *error
= localError
;
3056 CFReleaseSafe(localError
);
3060 ok
&= SOSDataSourceRelease(ds
, error
);
3061 CFReleaseNull(mdelete
);
3062 CFReleaseNull(madd
);
3063 CFReleaseNull(mnow
);
3068 ok
&= ks_close_keybag(bag_handle
, error
);
3074 CF_RETURNS_RETAINED CFDictionaryRef
3075 _SecServerBackupSyncable(CFDictionaryRef backup
, CFDataRef keybag
, CFDataRef password
, CFErrorRef
*error
) {
3076 require_action_quiet(isData(keybag
), errOut
, SecError(errSecParam
, error
, CFSTR("keybag %@ not a data"), keybag
));
3077 require_action_quiet(!backup
|| isDictionary(backup
), errOut
, SecError(errSecParam
, error
, CFSTR("backup %@ not a dictionary"), backup
));
3078 require_action_quiet(!password
|| isData(password
), errOut
, SecError(errSecParam
, error
, CFSTR("password %@ not a data"), password
));
3080 return _SecServerCopyTruthInTheCloud(keybag
, password
, backup
, error
);
3087 _SecServerRestoreSyncable(CFDictionaryRef backup
, CFDataRef keybag
, CFDataRef password
, CFErrorRef
*error
) {
3089 require_action_quiet(isData(keybag
), errOut
, ok
= SecError(errSecParam
, error
, CFSTR("keybag %@ not a data"), keybag
));
3090 require_action_quiet(isDictionary(backup
), errOut
, ok
= SecError(errSecParam
, error
, CFSTR("backup %@ not a dictionary"), backup
));
3093 require_action_quiet(isData(password
), errOut
, ok
= SecError(errSecParam
, error
, CFSTR("password not a data")));
3096 ok
= _SecServerRestoreTruthInTheCloud(keybag
, password
, backup
, error
);
3102 bool _SecServerRollKeysGlue(bool force
, CFErrorRef
*error
) {
3103 return _SecServerRollKeys(force
, NULL
, error
);
3107 bool _SecServerRollKeys(bool force
, SecurityClient
*client
, CFErrorRef
*error
) {
3109 uint32_t keystore_generation_status
= 0;
3110 if (aks_generation(KEYBAG_DEVICE
, generation_noop
, &keystore_generation_status
))
3112 uint32_t current_generation
= keystore_generation_status
& generation_current
;
3114 return kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
3115 bool up_to_date
= s3dl_dbt_keys_current(dbt
, current_generation
, NULL
);
3117 if (force
&& !up_to_date
) {
3118 up_to_date
= s3dl_dbt_update_keys(dbt
, client
, error
);
3120 secerror("Completed roll keys.");
3121 up_to_date
= s3dl_dbt_keys_current(dbt
, current_generation
, NULL
);
3124 secerror("Failed to roll keys.");
3134 InitialSyncItems(CFMutableArrayRef items
, bool limitToCurrent
, CFStringRef agrp
, CFStringRef svce
, const SecDbClass
*qclass
, CFErrorRef
*error
)
3136 bool result
= false;
3139 q
= query_create(qclass
, NULL
, NULL
, error
);
3142 q
->q_return_type
= kSecReturnDataMask
| kSecReturnAttributesMask
;
3143 q
->q_limit
= kSecMatchUnlimited
;
3144 q
->q_keybag
= KEYBAG_DEVICE
;
3146 query_add_attribute(kSecAttrAccessGroup
, agrp
, q
);
3147 query_add_attribute(kSecAttrSynchronizable
, kCFBooleanTrue
, q
);
3148 query_add_attribute(kSecAttrTombstone
, kCFBooleanFalse
, q
);
3150 query_add_attribute(kSecAttrService
, svce
, q
);
3152 result
= kc_with_dbt(false, error
, ^(SecDbConnectionRef dbt
) {
3153 return kc_transaction(dbt
, error
, ^{
3154 CFErrorRef error2
= NULL
;
3156 SecDbItemSelect(q
, dbt
, &error2
, NULL
, ^bool(const SecDbAttr
*attr
) {
3157 return CFDictionaryGetValue(q
->q_item
, attr
->name
);
3158 }, NULL
, NULL
, ^(SecDbItemRef item
, bool *stop
) {
3159 CFErrorRef error3
= NULL
;
3160 secinfo("InitialSyncItems", "Copy item");
3162 CFMutableDictionaryRef attrs
= SecDbItemCopyPListWithMask(item
, kSecDbSyncFlag
, &error3
);
3165 CFStringRef itemvwht
= CFDictionaryGetValue(attrs
, kSecAttrSyncViewHint
);
3167 * Saying its a SOS viewhint is really not the right answer post Triangle
3169 if (isString(itemvwht
) && !SOSViewInSOSSystem(itemvwht
)) {
3173 * Here we encode how PCS stores identities so that we only copy the
3174 * current identites for performance reasons.
3176 if (limitToCurrent
) {
3177 enum { PCS_CURRENT_IDENTITY_OFFSET
= 0x10000 };
3180 CFNumberRef type
= CFDictionaryGetValue(attrs
, kSecAttrType
);
3181 if (!isNumber(type
)) {
3182 // still allow this case since its not a service identity ??
3183 } else if (!CFNumberGetValue(type
, kCFNumberSInt32Type
, &s32
)) {
3185 } else if ((s32
& PCS_CURRENT_IDENTITY_OFFSET
) == 0) {
3190 CFDictionaryAddValue(attrs
, kSecClass
, SecDbItemGetClass(item
)->name
);
3191 CFArrayAppendValue(items
, attrs
);
3194 CFReleaseNull(attrs
);
3196 CFReleaseNull(error3
);
3198 CFReleaseNull(error2
);
3206 query_destroy(q
, NULL
);
3211 _SecServerCopyInitialSyncCredentials(uint32_t flags
, CFErrorRef
*error
)
3213 CFMutableArrayRef items
= CFArrayCreateMutableForCFTypes(NULL
);
3215 if (flags
& SecServerInitialSyncCredentialFlagTLK
) {
3216 require_action(InitialSyncItems(items
, false, CFSTR("com.apple.security.ckks"), NULL
, inet_class(), error
), fail
,
3217 secerror("failed to collect CKKS-inet keys: %@", error
? *error
: NULL
));
3219 if (flags
& SecServerInitialSyncCredentialFlagPCS
) {
3220 bool onlyCurrent
= !(flags
& SecServerInitialSyncCredentialFlagPCSNonCurrent
);
3222 require_action(InitialSyncItems(items
, false, CFSTR("com.apple.ProtectedCloudStorage"), NULL
, genp_class(), error
), fail
,
3223 secerror("failed to collect PCS-genp keys: %@", error
? *error
: NULL
));
3224 require_action(InitialSyncItems(items
, onlyCurrent
, CFSTR("com.apple.ProtectedCloudStorage"), NULL
, inet_class(), error
), fail
,
3225 secerror("failed to collect PCS-inet keys: %@", error
? *error
: NULL
));
3227 if (flags
& SecServerInitialSyncCredentialFlagBluetoothMigration
) {
3228 require_action(InitialSyncItems(items
, false, CFSTR("com.apple.nanoregistry.migration"), NULL
, genp_class(), error
), fail
,
3229 secerror("failed to collect com.apple.nanoregistry.migration-genp item: %@", error
? *error
: NULL
));
3230 require_action(InitialSyncItems(items
, false, CFSTR("com.apple.bluetooth"), CFSTR("BluetoothLESync"), genp_class(), error
), fail
,
3231 secerror("failed to collect com.apple.bluetooth-genp item: %@", error
? *error
: NULL
));
3240 _SecServerImportInitialSyncCredentials(CFArrayRef array
, CFErrorRef
*error
)
3242 return kc_with_dbt(true, error
, ^bool(SecDbConnectionRef dbt
) {
3243 return kc_transaction(dbt
, error
, ^bool(void){
3244 CFIndex n
, count
= CFArrayGetCount(array
);
3246 secinfo("ImportInitialSyncItems", "Importing %d items", (int)count
);
3248 for (n
= 0; n
< count
; n
++) {
3249 CFErrorRef cferror
= NULL
;
3251 CFDictionaryRef item
= CFArrayGetValueAtIndex(array
, n
);
3252 if (!isDictionary(item
))
3255 CFStringRef className
= CFDictionaryGetValue(item
, kSecClass
);
3256 if (className
== NULL
) {
3257 secinfo("ImportInitialSyncItems", "Item w/o class");
3261 const SecDbClass
*cls
= kc_class_with_name(className
);
3263 secinfo("ImportInitialSyncItems", "Item with unknown class: %@", className
);
3267 SecDbItemRef dbi
= SecDbItemCreateWithAttributes(NULL
, cls
, item
, KEYBAG_DEVICE
, &cferror
);
3269 secinfo("ImportInitialSyncItems", "Item creation failed with: %@", cferror
);
3270 CFReleaseNull(cferror
);
3274 if (!SecDbItemSetSyncable(dbi
, true, &cferror
)) {
3275 secinfo("ImportInitialSyncItems", "Failed to set sync=1: %@ for item %@", cferror
, dbi
);
3276 CFReleaseNull(cferror
);
3281 if (!SecDbItemInsert(dbi
, dbt
, &cferror
)) {
3282 secinfo("ImportInitialSyncItems", "Item store failed with: %@: %@", cferror
, dbi
);
3283 CFReleaseNull(cferror
);
3297 * Sync bubble migration code
3300 struct SyncBubbleRule
{
3301 CFStringRef attribute
;
3306 TransmogrifyItemsToSyncBubble(SecurityClient
*client
, uid_t uid
,
3309 const SecDbClass
*qclass
,
3310 struct SyncBubbleRule
*items
, CFIndex nItems
,
3313 CFMutableDictionaryRef updateAttributes
= NULL
;
3314 CFDataRef syncBubbleView
= NULL
;
3315 CFDataRef activeUserView
= NULL
;
3320 syncBubbleView
= SecMUSRCreateSyncBubbleUserUUID(uid
);
3321 require(syncBubbleView
, fail
);
3323 activeUserView
= SecMUSRCreateActiveUserUUID(uid
);
3324 require(activeUserView
, fail
);
3327 if ((onlyDelete
&& !copyToo
) || !onlyDelete
) {
3330 * Clean out items first
3333 secnotice("syncbubble", "cleaning out old items");
3335 q
= query_create(qclass
, NULL
, NULL
, error
);
3338 q
->q_limit
= kSecMatchUnlimited
;
3339 q
->q_keybag
= device_keybag_handle
;
3341 for (n
= 0; n
< nItems
; n
++) {
3342 query_add_attribute(items
[n
].attribute
, items
[n
].value
, q
);
3344 q
->q_musrView
= CFRetain(syncBubbleView
);
3345 require(q
->q_musrView
, fail
);
3347 kc_with_dbt(false, error
, ^(SecDbConnectionRef dbt
) {
3348 return kc_transaction(dbt
, error
, ^{
3349 return s3dl_query_delete(dbt
, q
, NULL
, error
);
3353 query_destroy(q
, NULL
);
3358 if (onlyDelete
|| !copyToo
) {
3359 secnotice("syncbubble", "skip migration of items");
3362 * Copy over items from EMCS to sync bubble
3365 secnotice("syncbubble", "migrating sync bubble items");
3367 q
= query_create(qclass
, NULL
, NULL
, error
);
3370 q
->q_return_type
= kSecReturnDataMask
| kSecReturnAttributesMask
;
3371 q
->q_limit
= kSecMatchUnlimited
;
3372 q
->q_keybag
= device_keybag_handle
; /* XXX change to session key bag when it exists */
3374 for (n
= 0; n
< nItems
; n
++) {
3375 query_add_or_attribute(items
[n
].attribute
, items
[n
].value
, q
);
3377 query_add_or_attribute(CFSTR("musr"), activeUserView
, q
);
3378 q
->q_musrView
= CFRetain(activeUserView
);
3380 updateAttributes
= CFDictionaryCreateMutableForCFTypes(NULL
);
3381 require(updateAttributes
, fail
);
3383 CFDictionarySetValue(updateAttributes
, CFSTR("musr"), syncBubbleView
); /* XXX should use kSecAttrMultiUser */
3386 kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
3387 return kc_transaction(dbt
, error
, ^{
3388 CFErrorRef error2
= NULL
;
3390 SecDbItemSelect(q
, dbt
, &error2
, NULL
, ^bool(const SecDbAttr
*attr
) {
3391 return CFDictionaryGetValue(q
->q_item
, attr
->name
);
3392 }, NULL
, NULL
, ^(SecDbItemRef item
, bool *stop
) {
3393 CFErrorRef error3
= NULL
;
3394 secinfo("syncbubble", "migrating item");
3396 SecDbItemRef new_item
= SecDbItemCopyWithUpdates(item
, updateAttributes
, NULL
);
3397 if (new_item
== NULL
)
3400 SecDbItemClearRowId(new_item
, NULL
);
3402 if (!SecDbItemSetKeybag(new_item
, device_keybag_handle
, NULL
)) {
3403 CFRelease(new_item
);
3407 if (!SecDbItemInsert(new_item
, dbt
, &error3
)) {
3408 secnotice("syncbubble", "migration failed with %@ for item %@", error3
, new_item
);
3410 CFRelease(new_item
);
3411 CFReleaseNull(error3
);
3413 CFReleaseNull(error2
);
3422 CFReleaseNull(syncBubbleView
);
3423 CFReleaseNull(activeUserView
);
3424 CFReleaseNull(updateAttributes
);
3426 query_destroy(q
, NULL
);
3431 static struct SyncBubbleRule PCSItems
[] = {
3433 .attribute
= CFSTR("agrp"),
3434 .value
= CFSTR("com.apple.ProtectedCloudStorage"),
3437 static struct SyncBubbleRule NSURLSesssiond
[] = {
3439 .attribute
= CFSTR("agrp"),
3440 .value
= CFSTR("com.apple.nsurlsessiond"),
3443 static struct SyncBubbleRule AccountsdItems
[] = {
3445 .attribute
= CFSTR("svce"),
3446 .value
= CFSTR("com.apple.account.AppleAccount.token"),
3449 .attribute
= CFSTR("svce"),
3450 .value
= CFSTR("com.apple.account.AppleAccount.password"),
3453 .attribute
= CFSTR("svce"),
3454 .value
= CFSTR("com.apple.account.AppleAccount.rpassword"),
3457 .attribute
= CFSTR("svce"),
3458 .value
= CFSTR("com.apple.account.idms.token"),
3461 .attribute
= CFSTR("svce"),
3462 .value
= CFSTR("com.apple.account.idms.continuation-key"),
3465 .attribute
= CFSTR("svce"),
3466 .value
= CFSTR("com.apple.account.CloudKit.token"),
3470 static struct SyncBubbleRule MobileMailItems
[] = {
3472 .attribute
= CFSTR("svce"),
3473 .value
= CFSTR("com.apple.account.IMAP.password"),
3476 .attribute
= CFSTR("svce"),
3477 .value
= CFSTR("com.apple.account.SMTP.password"),
3480 .attribute
= CFSTR("svce"),
3481 .value
= CFSTR("com.apple.account.Exchange.password"),
3484 .attribute
= CFSTR("svce"),
3485 .value
= CFSTR("com.apple.account.Hotmail.password"),
3488 .attribute
= CFSTR("svce"),
3489 .value
= CFSTR("com.apple.account.Google.password"),
3492 .attribute
= CFSTR("svce"),
3493 .value
= CFSTR("com.apple.account.Google.oauth-token"),
3496 .attribute
= CFSTR("svce"),
3497 .value
= CFSTR("com.apple.account.Google.oath-refresh-token"),
3500 .attribute
= CFSTR("svce"),
3501 .value
= CFSTR("com.apple.account.Yahoo.password"),
3504 .attribute
= CFSTR("svce"),
3505 .value
= CFSTR("com.apple.account.Yahoo.oauth-token"),
3508 .attribute
= CFSTR("svce"),
3509 .value
= CFSTR("com.apple.account.Yahoo.oauth-token-nosync"),
3512 .attribute
= CFSTR("svce"),
3513 .value
= CFSTR("com.apple.account.Yahoo.oath-refresh-token"),
3516 .attribute
= CFSTR("svce"),
3517 .value
= CFSTR("com.apple.account.IMAPNotes.password"),
3520 .attribute
= CFSTR("svce"),
3521 .value
= CFSTR("com.apple.account.IMAPMail.password"),
3524 .attribute
= CFSTR("svce"),
3525 .value
= CFSTR("com.apple.account.126.password"),
3528 .attribute
= CFSTR("svce"),
3529 .value
= CFSTR("com.apple.account.163.password"),
3532 .attribute
= CFSTR("svce"),
3533 .value
= CFSTR("com.apple.account.aol.password"),
3538 ArrayContains(CFArrayRef array
, CFStringRef service
)
3540 return CFArrayContainsValue(array
, CFRangeMake(0, CFArrayGetCount(array
)), service
);
3544 _SecServerTransmogrifyToSyncBubble(CFArrayRef services
, uid_t uid
, SecurityClient
*client
, CFErrorRef
*error
)
3546 bool copyCloudAuthToken
= false;
3547 bool copyMobileMail
= false;
3549 bool copyPCS
= false;
3550 bool onlyDelete
= false;
3551 bool copyNSURLSesssion
= false;
3553 if (!client
->inMultiUser
)
3556 secnotice("syncbubble", "migration for uid %d uid for services %@", (int)uid
, services
);
3558 #if TARGET_OS_SIMULATOR
3561 if (uid
!= (uid_t
)client
->activeUser
)
3564 #error "no sync bubble on other platforms"
3568 * First select that services to copy/delete
3571 if (ArrayContains(services
, CFSTR("com.apple.bird.usermanager.sync"))
3572 || ArrayContains(services
, CFSTR("com.apple.cloudphotod.sync"))
3573 || ArrayContains(services
, CFSTR("com.apple.cloudphotod.syncstakeholder"))
3574 || ArrayContains(services
, CFSTR("com.apple.cloudd.usermanager.sync")))
3576 copyCloudAuthToken
= true;
3580 if (ArrayContains(services
, CFSTR("com.apple.nsurlsessiond.usermanager.sync")))
3582 copyCloudAuthToken
= true;
3583 copyNSURLSesssion
= true;
3586 if (ArrayContains(services
, CFSTR("com.apple.syncdefaultsd.usermanager.sync"))) {
3587 copyCloudAuthToken
= true;
3589 if (ArrayContains(services
, CFSTR("com.apple.mailq.sync")) || ArrayContains(services
, CFSTR("com.apple.mailq.sync.xpc"))) {
3590 copyCloudAuthToken
= true;
3591 copyMobileMail
= true;
3596 * The actually copy/delete the items selected
3599 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyPCS
, inet_class(), PCSItems
, sizeof(PCSItems
)/sizeof(PCSItems
[0]), error
);
3601 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyPCS
, genp_class(), PCSItems
, sizeof(PCSItems
)/sizeof(PCSItems
[0]), error
);
3605 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyMobileMail
, genp_class(), MobileMailItems
, sizeof(MobileMailItems
)/sizeof(MobileMailItems
[0]), error
);
3609 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyCloudAuthToken
, genp_class(), AccountsdItems
, sizeof(AccountsdItems
)/sizeof(AccountsdItems
[0]), error
);
3613 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyNSURLSesssion
, inet_class(), NSURLSesssiond
, sizeof(NSURLSesssiond
)/sizeof(NSURLSesssiond
[0]), error
);
3621 * Migrate from user keychain to system keychain when switching to edu mode
3625 _SecServerTransmogrifyToSystemKeychain(SecurityClient
*client
, CFErrorRef
*error
)
3627 __block
bool ok
= true;
3630 * we are not in multi user yet, about to switch, otherwise we would
3631 * check that for client->inMultiuser here
3634 kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
3635 return kc_transaction(dbt
, error
, ^{
3636 CFDataRef systemUUID
= SecMUSRGetSystemKeychainUUID();
3638 const SecDbSchema
*newSchema
= current_schema();
3639 SecDbClass
const *const *kcClass
;
3641 for (kcClass
= newSchema
->classes
; *kcClass
!= NULL
; kcClass
++) {
3642 CFErrorRef localError
= NULL
;
3645 if (!((*kcClass
)->itemclass
)) {
3649 q
= query_create(*kcClass
, SecMUSRGetSingleUserKeychainUUID(), NULL
, error
);
3653 ok
&= SecDbItemSelect(q
, dbt
, error
, ^bool(const SecDbAttr
*attr
) {
3654 return (attr
->flags
& kSecDbInFlag
) != 0;
3655 }, ^bool(const SecDbAttr
*attr
) {
3656 // No filtering please.
3658 }, ^bool(CFMutableStringRef sql
, bool *needWhere
) {
3659 SecDbAppendWhereOrAnd(sql
, needWhere
);
3660 CFStringAppendFormat(sql
, NULL
, CFSTR("musr = ?"));
3662 }, ^bool(sqlite3_stmt
*stmt
, int col
) {
3663 return SecDbBindObject(stmt
, col
++, SecMUSRGetSingleUserKeychainUUID(), error
);
3664 }, ^(SecDbItemRef item
, bool *stop
) {
3665 CFErrorRef localError
= NULL
;
3667 if (!SecDbItemSetValueWithName(item
, kSecAttrMultiUser
, systemUUID
, &localError
)) {
3668 secerror("item: %@ update musr to system failed: %@", item
, localError
);
3673 if (!SecDbItemDoUpdate(item
, item
, dbt
, &localError
, ^bool (const SecDbAttr
*attr
) {
3674 return attr
->kind
== kSecDbRowIdAttr
;
3676 secerror("item: %@ insert during UPDATE: %@", item
, localError
);
3682 SecErrorPropagate(localError
, error
);
3686 query_destroy(q
, &localError
);
3697 * Delete account from local usage
3701 _SecServerDeleteMUSERViews(SecurityClient
*client
, uid_t uid
, CFErrorRef
*error
)
3703 return kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
3704 CFDataRef musrView
= NULL
, syncBubbleView
= NULL
;
3707 syncBubbleView
= SecMUSRCreateSyncBubbleUserUUID(uid
);
3708 require(syncBubbleView
, fail
);
3710 musrView
= SecMUSRCreateActiveUserUUID(uid
);
3711 require(musrView
, fail
);
3713 require(ok
= SecServerDeleteAllForUser(dbt
, syncBubbleView
, false, error
), fail
);
3714 require(ok
= SecServerDeleteAllForUser(dbt
, musrView
, false, error
), fail
);
3717 CFReleaseNull(syncBubbleView
);
3718 CFReleaseNull(musrView
);
3724 #endif /* TARGET_OS_IOS */
3727 _SecServerGetKeyStats(const SecDbClass
*qclass
,
3728 struct _SecServerKeyStats
*stats
)
3730 __block CFErrorRef error
= NULL
;
3733 Query
*q
= query_create(qclass
, NULL
, NULL
, &error
);
3736 q
->q_return_type
= kSecReturnDataMask
| kSecReturnAttributesMask
;
3737 q
->q_limit
= kSecMatchUnlimited
;
3738 q
->q_keybag
= KEYBAG_DEVICE
;
3739 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleWhenUnlocked
, q
);
3740 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleAfterFirstUnlock
, q
);
3741 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleAlways
, q
);
3742 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleWhenUnlockedThisDeviceOnly
, q
);
3743 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
, q
);
3744 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleAlwaysThisDeviceOnly
, q
);
3745 query_add_attribute(kSecAttrTombstone
, kCFBooleanFalse
, q
);
3747 kc_with_dbt(false, &error
, ^(SecDbConnectionRef dbconn
) {
3748 CFErrorRef error2
= NULL
;
3749 __block CFIndex totalSize
= 0;
3750 stats
->maxDataSize
= 0;
3752 SecDbItemSelect(q
, dbconn
, &error2
, NULL
, ^bool(const SecDbAttr
*attr
) {
3753 return CFDictionaryContainsKey(q
->q_item
, attr
->name
);
3754 }, NULL
, NULL
, ^(SecDbItemRef item
, bool *stop
) {
3755 CFErrorRef error3
= NULL
;
3756 CFDataRef data
= SecDbItemGetValue(item
, &v6v_Data
, &error3
);
3758 CFIndex size
= CFDataGetLength(data
);
3759 if (size
> stats
->maxDataSize
)
3760 stats
->maxDataSize
= size
;
3764 CFReleaseNull(error3
);
3766 CFReleaseNull(error2
);
3768 stats
->averageSize
= totalSize
/ stats
->items
;
3777 CFReleaseNull(error
);
3779 query_destroy(q
, NULL
);
3783 CFArrayRef
_SecItemCopyParentCertificates(CFDataRef normalizedIssuer
, CFArrayRef accessGroups
, CFErrorRef
*error
) {
3784 const void *keys
[] = {
3791 kSecClassCertificate
,
3796 CFDictionaryRef query
= CFDictionaryCreate(NULL
, keys
, values
, 4,
3798 CFTypeRef results
= NULL
;
3799 SecurityClient client
= {
3801 .accessGroups
= accessGroups
,
3802 .allowSystemKeychain
= true,
3803 .allowSyncBubbleKeychain
= false,
3804 .isNetworkExtension
= false,
3807 (void)_SecItemCopyMatching(query
, &client
, &results
, error
);
3812 bool _SecItemCertificateExists(CFDataRef normalizedIssuer
, CFDataRef serialNumber
, CFArrayRef accessGroups
, CFErrorRef
*error
) {
3813 const void *keys
[] = {
3817 kSecAttrSerialNumber
3820 kSecClassCertificate
,
3825 SecurityClient client
= {
3827 .accessGroups
= accessGroups
,
3828 .allowSystemKeychain
= true,
3829 .allowSyncBubbleKeychain
= false,
3830 .isNetworkExtension
= false,
3832 CFDictionaryRef query
= CFDictionaryCreate(NULL
, keys
, values
, 4, NULL
, NULL
);
3833 CFTypeRef results
= NULL
;
3834 bool ok
= _SecItemCopyMatching(query
, &client
, &results
, error
);
3835 CFReleaseSafe(query
);
3836 CFReleaseSafe(results
);