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 return kc_with_dbt(true, error
, ^bool (SecDbConnectionRef dbt
) {
2021 return (kc_transaction(dbt
, error
, ^bool {
2022 return (SecDbExec(dbt
, CFSTR("DELETE from genp;"), error
) &&
2023 SecDbExec(dbt
, CFSTR("DELETE from inet;"), error
) &&
2024 SecDbExec(dbt
, CFSTR("DELETE from cert;"), error
) &&
2025 SecDbExec(dbt
, CFSTR("DELETE from keys;"), error
));
2026 }) && SecDbExec(dbt
, CFSTR("VACUUM;"), error
));
2031 _SecItemDeleteAll(CFErrorRef
*error
) {
2032 return SecItemServerDeleteAll(error
);
2036 _SecItemServerDeleteAllWithAccessGroups(CFArrayRef accessGroups
, SecurityClient
*client
, CFErrorRef
*error
)
2038 __block
bool ok
= true;
2039 static dispatch_once_t onceToken
;
2040 static CFSetRef illegalAccessGroups
= NULL
;
2042 dispatch_once(&onceToken
, ^{
2043 const CFStringRef values
[] = {
2046 CFSTR("com.apple.security.sos"),
2047 CFSTR("lockdown-identities"),
2049 illegalAccessGroups
= CFSetCreate(NULL
, (const void **)values
, sizeof(values
)/sizeof(values
[0]), &kCFTypeSetCallBacks
);
2052 static CFTypeRef qclasses
[] = {
2058 // strange construction needed for schema indirection
2059 static dispatch_once_t qclassesOnceToken
;
2060 dispatch_once(&qclassesOnceToken
, ^{
2061 qclasses
[0] = inet_class();
2062 qclasses
[1] = genp_class();
2063 qclasses
[2] = keys_class();
2064 qclasses
[3] = cert_class();
2067 require_action_quiet(isArray(accessGroups
), fail
,
2069 SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType
, sSecXPCErrorDomain
, NULL
, error
, NULL
, CFSTR("accessGroups not CFArray, got %@"), accessGroups
));
2071 // TODO: whitelist instead? look for dev IDs like 7123498YQX.com.somedev.app
2073 require_action(CFArrayGetCount(accessGroups
) != 0, fail
,
2075 SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType
, sSecXPCErrorDomain
, NULL
, error
, NULL
, CFSTR("accessGroups e empty")));
2078 // Pre-check accessGroups for prohibited values
2079 CFArrayForEach(accessGroups
, ^(const void *value
) {
2080 CFStringRef agrp
= (CFStringRef
)value
;
2082 if (!isString(agrp
)) {
2083 SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType
, sSecXPCErrorDomain
, NULL
, error
, NULL
,
2084 CFSTR("access not a string: %@"), agrp
);
2086 } else if (CFSetContainsValue(illegalAccessGroups
, agrp
)) {
2087 SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType
, sSecXPCErrorDomain
, NULL
, error
, NULL
,
2088 CFSTR("illegal access group: %@"), accessGroups
);
2094 ok
= kc_with_dbt(true, error
, ^bool(SecDbConnectionRef dbt
) {
2095 return kc_transaction(dbt
, error
, ^bool {
2096 CFErrorRef localError
= NULL
;
2100 for (n
= 0; n
< sizeof(qclasses
)/sizeof(qclasses
[0]) && ok1
; n
++) {
2103 q
= query_create(qclasses
[n
], client
->musr
, NULL
, error
);
2106 (void)s3dl_query_delete(dbt
, q
, accessGroups
, &localError
);
2108 query_destroy(q
, error
);
2109 CFReleaseNull(localError
);
2112 }) && SecDbExec(dbt
, CFSTR("VACUUM"), error
);
2121 // MARK: Shared web credentials
2123 #if TARGET_OS_IOS && !TARGET_OS_BRIDGE
2126 #define SEC_CONST_DECL(k,v) const CFStringRef k = CFSTR(v);
2128 SEC_CONST_DECL (kSecSafariAccessGroup
, "com.apple.cfnetwork");
2129 SEC_CONST_DECL (kSecSafariDefaultComment
, "default");
2130 SEC_CONST_DECL (kSecSafariPasswordsNotSaved
, "Passwords not saved");
2131 SEC_CONST_DECL (kSecSharedCredentialUrlScheme
, "https://");
2132 SEC_CONST_DECL (kSecSharedWebCredentialsService
, "webcredentials");
2134 #if !TARGET_IPHONE_SIMULATOR
2136 _SecAppDomainApprovalStatus(CFStringRef appID
, CFStringRef fqdn
, CFErrorRef
*error
)
2138 __block SWCFlags flags
= kSWCFlags_None
;
2141 dispatch_semaphore_t semaphore
= dispatch_semaphore_create(0);
2142 if (semaphore
== NULL
)
2145 status
= SWCCheckService(kSecSharedWebCredentialsService
, appID
, fqdn
, ^void (OSStatus inStatus
, SWCFlags inFlags
, CFDictionaryRef inDetails
)
2147 if (inStatus
== 0) {
2150 secerror("SWCCheckService failed with %d", (int)inStatus
);
2152 dispatch_semaphore_signal(semaphore
);
2156 dispatch_semaphore_wait(semaphore
, DISPATCH_TIME_FOREVER
);
2158 secerror("SWCCheckService: failed to queue");
2160 dispatch_release(semaphore
);
2163 if (!(flags
& kSWCFlag_SiteApproved
)) {
2164 if (flags
& kSWCFlag_Pending
) {
2165 SecError(errSecAuthFailed
, error
, CFSTR("Approval is pending for \"%@\", try later"), fqdn
);
2167 SecError(errSecAuthFailed
, error
, CFSTR("\"%@\" failed to approve \"%@\""), fqdn
, appID
);
2169 } else if (flags
& kSWCFlag_UserDenied
) {
2170 SecError(errSecAuthFailed
, error
, CFSTR("User denied access to \"%@\" by \"%@\""), fqdn
, appID
);
2177 _SecEntitlementContainsDomainForService(CFArrayRef domains
, CFStringRef domain
, CFStringRef service
)
2179 bool result
= false;
2180 CFIndex idx
, count
= (domains
) ? CFArrayGetCount(domains
) : (CFIndex
) 0;
2181 if (!count
|| !domain
|| !service
) {
2184 for (idx
=0; idx
< count
; idx
++) {
2185 CFStringRef str
= (CFStringRef
) CFArrayGetValueAtIndex(domains
, idx
);
2186 if (str
&& CFStringHasPrefix(str
, kSecSharedWebCredentialsService
)) {
2187 CFIndex prefix_len
= CFStringGetLength(kSecSharedWebCredentialsService
)+1;
2188 CFIndex substr_len
= CFStringGetLength(str
) - prefix_len
;
2189 CFRange range
= { prefix_len
, substr_len
};
2190 CFStringRef substr
= CFStringCreateWithSubstring(kCFAllocatorDefault
, str
, range
);
2191 if (substr
&& CFEqual(substr
, domain
)) {
2194 CFReleaseSafe(substr
);
2202 #endif /* !TARGET_OS_SIMULATOR */
2205 _SecAddNegativeWebCredential(SecurityClient
*client
, CFStringRef fqdn
, CFStringRef appID
, bool forSafari
)
2207 #if !TARGET_IPHONE_SIMULATOR
2208 bool result
= false;
2209 if (!fqdn
) { return result
; }
2211 // update our database
2212 CFRetainSafe(appID
);
2214 if (0 == SWCSetServiceFlags(kSecSharedWebCredentialsService
, appID
, fqdn
, kSWCFlag_ExternalMask
, kSWCFlag_UserDenied
,
2215 ^void(OSStatus inStatus
, SWCFlags inNewFlags
){
2216 CFReleaseSafe(appID
);
2217 CFReleaseSafe(fqdn
);
2222 else // didn't queue the block
2224 CFReleaseSafe(appID
);
2225 CFReleaseSafe(fqdn
);
2228 if (!forSafari
) { return result
; }
2230 // below this point: create a negative Safari web credential item
2232 CFMutableDictionaryRef attrs
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2233 if (!attrs
) { return result
; }
2235 CFErrorRef error
= NULL
;
2236 CFStringRef accessGroup
= CFSTR("*");
2237 SecurityClient swcclient
= {
2239 .accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&accessGroup
, 1, &kCFTypeArrayCallBacks
),
2240 .allowSystemKeychain
= false,
2241 .allowSyncBubbleKeychain
= false,
2242 .isNetworkExtension
= false,
2243 .musr
= client
->musr
,
2246 CFDictionaryAddValue(attrs
, kSecClass
, kSecClassInternetPassword
);
2247 CFDictionaryAddValue(attrs
, kSecAttrAccessGroup
, kSecSafariAccessGroup
);
2248 CFDictionaryAddValue(attrs
, kSecAttrAuthenticationType
, kSecAttrAuthenticationTypeHTMLForm
);
2249 CFDictionaryAddValue(attrs
, kSecAttrProtocol
, kSecAttrProtocolHTTPS
);
2250 CFDictionaryAddValue(attrs
, kSecAttrServer
, fqdn
);
2251 CFDictionaryAddValue(attrs
, kSecAttrSynchronizable
, kCFBooleanTrue
);
2253 (void)_SecItemDelete(attrs
, &swcclient
, &error
);
2254 CFReleaseNull(error
);
2256 CFDictionaryAddValue(attrs
, kSecAttrAccount
, kSecSafariPasswordsNotSaved
);
2257 CFDictionaryAddValue(attrs
, kSecAttrComment
, kSecSafariDefaultComment
);
2259 CFStringRef label
= CFStringCreateWithFormat(kCFAllocatorDefault
,
2260 NULL
, CFSTR("%@ (%@)"), fqdn
, kSecSafariPasswordsNotSaved
);
2262 CFDictionaryAddValue(attrs
, kSecAttrLabel
, label
);
2263 CFReleaseSafe(label
);
2267 CFDataRef data
= CFDataCreate(kCFAllocatorDefault
, &space
, 1);
2269 CFDictionarySetValue(attrs
, kSecValueData
, data
);
2270 CFReleaseSafe(data
);
2273 CFTypeRef addResult
= NULL
;
2274 result
= _SecItemAdd(attrs
, &swcclient
, &addResult
, &error
);
2276 CFReleaseSafe(addResult
);
2277 CFReleaseSafe(error
);
2278 CFReleaseSafe(attrs
);
2279 CFReleaseSafe(swcclient
.accessGroups
);
2287 /* Specialized version of SecItemAdd for shared web credentials */
2289 _SecAddSharedWebCredential(CFDictionaryRef attributes
,
2290 SecurityClient
*client
,
2291 const audit_token_t
*clientAuditToken
,
2298 SecurityClient swcclient
= {};
2300 CFStringRef fqdn
= CFRetainSafe(CFDictionaryGetValue(attributes
, kSecAttrServer
));
2301 CFStringRef account
= CFDictionaryGetValue(attributes
, kSecAttrAccount
);
2302 #if TARGET_OS_IOS && !TARGET_OS_BRIDGE
2303 CFStringRef password
= CFDictionaryGetValue(attributes
, kSecSharedPassword
);
2305 CFStringRef password
= CFDictionaryGetValue(attributes
, CFSTR("spwd"));
2307 CFStringRef accessGroup
= CFSTR("*");
2308 CFMutableDictionaryRef query
= NULL
, attrs
= NULL
;
2312 // check autofill enabled status
2313 if (!swca_autofill_enabled(clientAuditToken
)) {
2314 SecError(errSecBadReq
, error
, CFSTR("Autofill is not enabled in Safari settings"));
2318 // parse fqdn with CFURL here, since it could be specified as domain:port
2320 CFStringRef urlStr
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%@%@"), kSecSharedCredentialUrlScheme
, fqdn
);
2322 CFURLRef url
= CFURLCreateWithString(kCFAllocatorDefault
, urlStr
, nil
);
2324 CFStringRef hostname
= CFURLCopyHostName(url
);
2326 CFReleaseSafe(fqdn
);
2328 port
= CFURLGetPortNumber(url
);
2332 CFReleaseSafe(urlStr
);
2337 SecError(errSecParam
, error
, CFSTR("No account provided"));
2341 SecError(errSecParam
, error
, CFSTR("No domain provided"));
2345 #if TARGET_IPHONE_SIMULATOR
2346 secerror("app/site association entitlements not checked in Simulator");
2348 OSStatus status
= errSecMissingEntitlement
;
2349 // validate that fqdn is part of caller's shared credential domains entitlement
2351 SecError(status
, error
, CFSTR("Missing application-identifier entitlement"));
2354 if (_SecEntitlementContainsDomainForService(domains
, fqdn
, kSecSharedWebCredentialsService
)) {
2355 status
= errSecSuccess
;
2357 if (errSecSuccess
!= status
) {
2358 CFStringRef msg
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
,
2359 CFSTR("%@ not found in %@ entitlement"), fqdn
, kSecEntitlementAssociatedDomains
);
2361 msg
= CFRetain(CFSTR("Requested domain not found in entitlement"));
2363 SecError(status
, error
, CFSTR("%@"), msg
);
2369 #if TARGET_IPHONE_SIMULATOR
2370 secerror("Ignoring app/site approval state in the Simulator.");
2372 // get approval status for this app/domain pair
2373 SWCFlags flags
= _SecAppDomainApprovalStatus(appID
, fqdn
, error
);
2374 if (!(flags
& kSWCFlag_SiteApproved
)) {
2379 // give ourselves access to see matching items for kSecSafariAccessGroup
2380 swcclient
.task
= NULL
;
2381 swcclient
.accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&accessGroup
, 1, &kCFTypeArrayCallBacks
);
2382 swcclient
.allowSystemKeychain
= false;
2383 swcclient
.musr
= client
->musr
;
2384 swcclient
.allowSystemKeychain
= false;
2385 swcclient
.allowSyncBubbleKeychain
= false;
2386 swcclient
.isNetworkExtension
= false;
2389 // create lookup query
2390 query
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2392 SecError(errSecAllocate
, error
, CFSTR("Unable to create query dictionary"));
2395 CFDictionaryAddValue(query
, kSecClass
, kSecClassInternetPassword
);
2396 CFDictionaryAddValue(query
, kSecAttrAccessGroup
, kSecSafariAccessGroup
);
2397 CFDictionaryAddValue(query
, kSecAttrAuthenticationType
, kSecAttrAuthenticationTypeHTMLForm
);
2398 CFDictionaryAddValue(query
, kSecAttrServer
, fqdn
);
2399 CFDictionaryAddValue(query
, kSecAttrSynchronizable
, kCFBooleanTrue
);
2401 // check for presence of Safari's negative entry ('passwords not saved')
2402 CFDictionarySetValue(query
, kSecAttrAccount
, kSecSafariPasswordsNotSaved
);
2403 ok
= _SecItemCopyMatching(query
, &swcclient
, result
, error
);
2404 if(result
) CFReleaseNull(*result
);
2405 if (error
) CFReleaseNull(*error
);
2407 SecError(errSecDuplicateItem
, error
, CFSTR("Item already exists for this server"));
2411 // now use the provided account (and optional port number, if one was present)
2412 CFDictionarySetValue(query
, kSecAttrAccount
, account
);
2413 if (port
< -1 || port
> 0) {
2414 SInt16 portValueShort
= (port
& 0xFFFF);
2415 CFNumberRef portNumber
= CFNumberCreate(NULL
, kCFNumberSInt16Type
, &portValueShort
);
2416 CFDictionaryAddValue(query
, kSecAttrPort
, portNumber
);
2417 CFReleaseSafe(portNumber
);
2420 // look up existing password
2421 CFDictionaryAddValue(query
, kSecReturnData
, kCFBooleanTrue
);
2422 bool matched
= _SecItemCopyMatching(query
, &swcclient
, result
, error
);
2423 CFDictionaryRemoveValue(query
, kSecReturnData
);
2425 // found it, so this becomes either an "update password" or "delete password" operation
2426 bool update
= (password
!= NULL
);
2428 attrs
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2429 CFDataRef credential
= CFStringCreateExternalRepresentation(kCFAllocatorDefault
, password
, kCFStringEncodingUTF8
, 0);
2430 CFDictionaryAddValue(attrs
, kSecValueData
, credential
);
2431 bool samePassword
= result
&& *result
&& CFEqual(*result
, credential
);
2432 CFReleaseSafe(credential
);
2433 CFDictionaryAddValue(attrs
, kSecAttrComment
, kSecSafariDefaultComment
);
2435 ok
= samePassword
|| swca_confirm_operation(swca_update_request_id
, clientAuditToken
, query
, error
,
2436 ^void (CFStringRef fqdn
) { _SecAddNegativeWebCredential(client
, fqdn
, appID
, false); });
2438 ok
= _SecItemUpdate(query
, attrs
, &swcclient
, error
);
2442 // confirm the delete
2443 // (per rdar://16676288 we always prompt, even if there was prior user approval)
2444 ok
= /*approved ||*/ swca_confirm_operation(swca_delete_request_id
, clientAuditToken
, query
, error
,
2445 ^void (CFStringRef fqdn
) { _SecAddNegativeWebCredential(client
, fqdn
, appID
, false); });
2447 ok
= _SecItemDelete(query
, &swcclient
, error
);
2451 if(result
) CFReleaseNull(*result
);
2452 if(error
) CFReleaseNull(*error
);
2456 if (result
) CFReleaseNull(*result
);
2457 if (error
) CFReleaseNull(*error
);
2459 // password does not exist, so prepare to add it
2461 // a NULL password value removes the existing credential. Since we didn't find it, this is a no-op.
2466 CFStringRef label
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%@ (%@)"), fqdn
, account
);
2468 CFDictionaryAddValue(query
, kSecAttrLabel
, label
);
2469 CFReleaseSafe(label
);
2471 // NOTE: we always expect to use HTTPS for web forms.
2472 CFDictionaryAddValue(query
, kSecAttrProtocol
, kSecAttrProtocolHTTPS
);
2474 CFDataRef credential
= CFStringCreateExternalRepresentation(kCFAllocatorDefault
, password
, kCFStringEncodingUTF8
, 0);
2475 CFDictionarySetValue(query
, kSecValueData
, credential
);
2476 CFReleaseSafe(credential
);
2477 CFDictionarySetValue(query
, kSecAttrComment
, kSecSafariDefaultComment
);
2479 CFReleaseSafe(swcclient
.accessGroups
);
2480 swcclient
.accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&kSecSafariAccessGroup
, 1, &kCFTypeArrayCallBacks
);
2482 // mark the item as created by this function
2483 const int32_t creator_value
= 'swca';
2484 CFNumberRef creator
= CFNumberCreate(kCFAllocatorDefault
, kCFNumberSInt32Type
, &creator_value
);
2486 CFDictionarySetValue(query
, kSecAttrCreator
, creator
);
2487 CFReleaseSafe(creator
);
2492 // (per rdar://16680019, we won't prompt here in the normal case)
2493 ok
= /*approved ||*/ swca_confirm_operation(swca_add_request_id
, clientAuditToken
, query
, error
,
2494 ^void (CFStringRef fqdn
) { _SecAddNegativeWebCredential(client
, fqdn
, appID
, false); });
2498 ok
= _SecItemAdd(query
, &swcclient
, result
, error
);
2502 CFReleaseSafe(attrs
);
2503 CFReleaseSafe(query
);
2504 CFReleaseSafe(swcclient
.accessGroups
);
2505 CFReleaseSafe(fqdn
);
2509 /* Specialized version of SecItemCopyMatching for shared web credentials */
2511 _SecCopySharedWebCredential(CFDictionaryRef query
,
2512 SecurityClient
*client
,
2513 const audit_token_t
*clientAuditToken
,
2519 CFMutableArrayRef credentials
= NULL
;
2520 CFMutableArrayRef foundItems
= NULL
;
2521 CFMutableArrayRef fqdns
= NULL
;
2522 CFStringRef fqdn
= NULL
;
2523 CFStringRef account
= NULL
;
2528 require_quiet(result
, cleanup
);
2529 credentials
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
2530 foundItems
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
2531 fqdns
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
2533 // give ourselves access to see matching items for kSecSafariAccessGroup
2534 CFStringRef accessGroup
= CFSTR("*");
2535 SecurityClient swcclient
= {
2537 .accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&accessGroup
, 1, &kCFTypeArrayCallBacks
),
2538 .allowSystemKeychain
= false,
2539 .allowSyncBubbleKeychain
= false,
2540 .isNetworkExtension
= false,
2541 .musr
= client
->musr
,
2544 // On input, the query dictionary contains optional fqdn and account entries.
2545 fqdn
= CFDictionaryGetValue(query
, kSecAttrServer
);
2546 account
= CFDictionaryGetValue(query
, kSecAttrAccount
);
2548 // Check autofill enabled status
2549 if (!swca_autofill_enabled(clientAuditToken
)) {
2550 SecError(errSecBadReq
, error
, CFSTR("Autofill is not enabled in Safari settings"));
2554 // Check fqdn; if NULL, add domains from caller's entitlement.
2556 CFArrayAppendValue(fqdns
, fqdn
);
2559 CFIndex idx
, count
= CFArrayGetCount(domains
);
2560 for (idx
=0; idx
< count
; idx
++) {
2561 CFStringRef str
= (CFStringRef
) CFArrayGetValueAtIndex(domains
, idx
);
2562 // Parse the entry for our service label prefix
2563 if (str
&& CFStringHasPrefix(str
, kSecSharedWebCredentialsService
)) {
2564 CFIndex prefix_len
= CFStringGetLength(kSecSharedWebCredentialsService
)+1;
2565 CFIndex substr_len
= CFStringGetLength(str
) - prefix_len
;
2566 CFRange range
= { prefix_len
, substr_len
};
2567 fqdn
= CFStringCreateWithSubstring(kCFAllocatorDefault
, str
, range
);
2569 CFArrayAppendValue(fqdns
, fqdn
);
2575 count
= CFArrayGetCount(fqdns
);
2577 SecError(errSecParam
, error
, CFSTR("No domain provided"));
2581 // Aggregate search results for each domain
2582 for (idx
= 0; idx
< count
; idx
++) {
2583 CFMutableArrayRef items
= NULL
;
2584 CFMutableDictionaryRef attrs
= NULL
;
2585 fqdn
= (CFStringRef
) CFArrayGetValueAtIndex(fqdns
, idx
);
2589 // Parse the fqdn for a possible port specifier.
2591 CFStringRef urlStr
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%@%@"), kSecSharedCredentialUrlScheme
, fqdn
);
2593 CFURLRef url
= CFURLCreateWithString(kCFAllocatorDefault
, urlStr
, nil
);
2595 CFStringRef hostname
= CFURLCopyHostName(url
);
2597 CFReleaseSafe(fqdn
);
2599 port
= CFURLGetPortNumber(url
);
2603 CFReleaseSafe(urlStr
);
2607 #if TARGET_IPHONE_SIMULATOR
2608 secerror("app/site association entitlements not checked in Simulator");
2610 OSStatus status
= errSecMissingEntitlement
;
2612 SecError(status
, error
, CFSTR("Missing application-identifier entitlement"));
2613 CFReleaseSafe(fqdn
);
2616 // validate that fqdn is part of caller's entitlement
2617 if (_SecEntitlementContainsDomainForService(domains
, fqdn
, kSecSharedWebCredentialsService
)) {
2618 status
= errSecSuccess
;
2620 if (errSecSuccess
!= status
) {
2621 CFStringRef msg
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
,
2622 CFSTR("%@ not found in %@ entitlement"), fqdn
, kSecEntitlementAssociatedDomains
);
2624 msg
= CFRetain(CFSTR("Requested domain not found in entitlement"));
2626 SecError(status
, error
, CFSTR("%@"), msg
);
2628 CFReleaseSafe(fqdn
);
2633 attrs
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2635 SecError(errSecAllocate
, error
, CFSTR("Unable to create query dictionary"));
2636 CFReleaseSafe(fqdn
);
2639 CFDictionaryAddValue(attrs
, kSecClass
, kSecClassInternetPassword
);
2640 CFDictionaryAddValue(attrs
, kSecAttrAccessGroup
, kSecSafariAccessGroup
);
2641 CFDictionaryAddValue(attrs
, kSecAttrProtocol
, kSecAttrProtocolHTTPS
);
2642 CFDictionaryAddValue(attrs
, kSecAttrAuthenticationType
, kSecAttrAuthenticationTypeHTMLForm
);
2643 CFDictionaryAddValue(attrs
, kSecAttrServer
, fqdn
);
2645 CFDictionaryAddValue(attrs
, kSecAttrAccount
, account
);
2647 if (port
< -1 || port
> 0) {
2648 SInt16 portValueShort
= (port
& 0xFFFF);
2649 CFNumberRef portNumber
= CFNumberCreate(NULL
, kCFNumberSInt16Type
, &portValueShort
);
2650 CFDictionaryAddValue(attrs
, kSecAttrPort
, portNumber
);
2651 CFReleaseSafe(portNumber
);
2653 CFDictionaryAddValue(attrs
, kSecAttrSynchronizable
, kCFBooleanTrue
);
2654 CFDictionaryAddValue(attrs
, kSecMatchLimit
, kSecMatchLimitAll
);
2655 CFDictionaryAddValue(attrs
, kSecReturnAttributes
, kCFBooleanTrue
);
2656 CFDictionaryAddValue(attrs
, kSecReturnData
, kCFBooleanTrue
);
2658 ok
= _SecItemCopyMatching(attrs
, &swcclient
, (CFTypeRef
*)&items
, error
);
2660 // ignore interim error since we have multiple domains to search
2661 CFReleaseNull(*error
);
2663 if (ok
&& items
&& CFGetTypeID(items
) == CFArrayGetTypeID()) {
2664 #if TARGET_IPHONE_SIMULATOR
2665 secerror("Ignoring app/site approval state in the Simulator.");
2666 bool approved
= true;
2668 // get approval status for this app/domain pair
2669 SWCFlags flags
= _SecAppDomainApprovalStatus(appID
, fqdn
, error
);
2671 // ignore interim error since we have multiple domains to check
2672 CFReleaseNull(*error
);
2674 bool approved
= (flags
& kSWCFlag_SiteApproved
);
2677 CFArrayAppendArray(foundItems
, items
, CFRangeMake(0, CFArrayGetCount(items
)));
2680 CFReleaseSafe(items
);
2681 CFReleaseSafe(attrs
);
2682 CFReleaseSafe(fqdn
);
2685 // If matching credentials are found, the credentials provided to the completionHandler
2686 // will be a CFArrayRef containing CFDictionaryRef entries. Each dictionary entry will
2687 // contain the following pairs (see Security/SecItem.h):
2688 // key: kSecAttrServer value: CFStringRef (the website)
2689 // key: kSecAttrAccount value: CFStringRef (the account)
2690 // key: kSecSharedPassword value: CFStringRef (the password)
2692 // key: kSecAttrPort value: CFNumberRef (the port number, if non-standard for https)
2694 count
= CFArrayGetCount(foundItems
);
2695 for (idx
= 0; idx
< count
; idx
++) {
2696 CFDictionaryRef dict
= (CFDictionaryRef
) CFArrayGetValueAtIndex(foundItems
, idx
);
2697 CFMutableDictionaryRef newdict
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2698 if (newdict
&& dict
&& CFGetTypeID(dict
) == CFDictionaryGetTypeID()) {
2699 CFStringRef srvr
= CFDictionaryGetValue(dict
, kSecAttrServer
);
2700 CFStringRef acct
= CFDictionaryGetValue(dict
, kSecAttrAccount
);
2701 CFNumberRef pnum
= CFDictionaryGetValue(dict
, kSecAttrPort
);
2702 CFStringRef icmt
= CFDictionaryGetValue(dict
, kSecAttrComment
);
2703 CFDataRef data
= CFDictionaryGetValue(dict
, kSecValueData
);
2705 CFDictionaryAddValue(newdict
, kSecAttrServer
, srvr
);
2708 CFDictionaryAddValue(newdict
, kSecAttrAccount
, acct
);
2712 if (CFNumberGetValue(pnum
, kCFNumberSInt16Type
, &pval
) &&
2713 (pval
< -1 || pval
> 0)) {
2714 CFDictionaryAddValue(newdict
, kSecAttrPort
, pnum
);
2718 CFStringRef password
= CFStringCreateFromExternalRepresentation(kCFAllocatorDefault
, data
, kCFStringEncodingUTF8
);
2720 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH && !TARGET_OS_TV
2721 CFDictionaryAddValue(newdict
, kSecSharedPassword
, password
);
2723 CFDictionaryAddValue(newdict
, CFSTR("spwd"), password
);
2725 CFReleaseSafe(password
);
2728 if (icmt
&& CFEqual(icmt
, kSecSafariDefaultComment
)) {
2729 CFArrayInsertValueAtIndex(credentials
, 0, newdict
);
2731 CFArrayAppendValue(credentials
, newdict
);
2734 CFReleaseSafe(newdict
);
2741 // create a new array of dictionaries (without the actual password) for picker UI
2742 count
= CFArrayGetCount(credentials
);
2743 CFMutableArrayRef items
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
2744 for (idx
= 0; idx
< count
; idx
++) {
2745 CFDictionaryRef dict
= (CFDictionaryRef
) CFArrayGetValueAtIndex(credentials
, idx
);
2746 CFMutableDictionaryRef newdict
= CFDictionaryCreateMutableCopy(kCFAllocatorDefault
, 0, dict
);
2747 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH && !TARGET_OS_TV
2748 CFDictionaryRemoveValue(newdict
, kSecSharedPassword
);
2750 CFDictionaryRemoveValue(newdict
, CFSTR("spwd"));
2752 CFArrayAppendValue(items
, newdict
);
2753 CFReleaseSafe(newdict
);
2756 // prompt user to select one of the dictionary items
2757 CFDictionaryRef selected
= swca_copy_selected_dictionary(swca_select_request_id
,
2758 clientAuditToken
, items
, error
);
2760 // find the matching item in our credentials array
2761 CFStringRef srvr
= CFDictionaryGetValue(selected
, kSecAttrServer
);
2762 CFStringRef acct
= CFDictionaryGetValue(selected
, kSecAttrAccount
);
2763 CFNumberRef pnum
= CFDictionaryGetValue(selected
, kSecAttrPort
);
2764 for (idx
= 0; idx
< count
; idx
++) {
2765 CFDictionaryRef dict
= (CFDictionaryRef
) CFArrayGetValueAtIndex(credentials
, idx
);
2766 CFStringRef srvr1
= CFDictionaryGetValue(dict
, kSecAttrServer
);
2767 CFStringRef acct1
= CFDictionaryGetValue(dict
, kSecAttrAccount
);
2768 CFNumberRef pnum1
= CFDictionaryGetValue(dict
, kSecAttrPort
);
2770 if (!srvr
|| !srvr1
|| !CFEqual(srvr
, srvr1
)) continue;
2771 if (!acct
|| !acct1
|| !CFEqual(acct
, acct1
)) continue;
2772 if ((pnum
&& pnum1
) && !CFEqual(pnum
, pnum1
)) continue;
2775 CFReleaseSafe(selected
);
2782 CFReleaseSafe(items
);
2783 CFArrayRemoveAllValues(credentials
);
2784 if (selected
&& ok
) {
2785 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
2786 fqdn
= CFDictionaryGetValue(selected
, kSecAttrServer
);
2788 CFArrayAppendValue(credentials
, selected
);
2792 #if TARGET_OS_IOS && !TARGET_OS_BRIDGE && !TARGET_IPHONE_SIMULATOR
2793 // register confirmation with database
2794 CFRetainSafe(appID
);
2796 if (0 != SWCSetServiceFlags(kSecSharedWebCredentialsService
,
2797 appID
, fqdn
, kSWCFlag_ExternalMask
, kSWCFlag_UserApproved
,
2798 ^void(OSStatus inStatus
, SWCFlags inNewFlags
){
2799 CFReleaseSafe(appID
);
2800 CFReleaseSafe(fqdn
);
2803 // we didn't queue the block
2804 CFReleaseSafe(appID
);
2805 CFReleaseSafe(fqdn
);
2809 CFReleaseSafe(selected
);
2811 else if (NULL
== *error
) {
2812 // found no items, and we haven't already filled in the error
2813 SecError(errSecItemNotFound
, error
, CFSTR("no matching items found"));
2818 CFArrayRemoveAllValues(credentials
);
2819 CFReleaseNull(credentials
);
2821 CFReleaseSafe(foundItems
);
2822 *result
= credentials
;
2823 CFReleaseSafe(swcclient
.accessGroups
);
2824 CFReleaseSafe(fqdns
);
2829 #endif /* TARGET_OS_IOS */
2833 // MARK: Keychain backup
2835 CF_RETURNS_RETAINED CFDataRef
2836 _SecServerKeychainCreateBackup(SecurityClient
*client
, CFDataRef keybag
, CFDataRef passcode
, bool emcs
, CFErrorRef
*error
) {
2837 __block CFDataRef backup
;
2838 kc_with_dbt(true, error
, ^bool (SecDbConnectionRef dbt
) {
2842 if (keybag
== NULL
&& passcode
== NULL
) {
2844 backup
= SecServerExportBackupableKeychain(dbt
, client
, KEYBAG_DEVICE
, backup_keybag_handle
, error
);
2845 #else /* !USE_KEYSTORE */
2847 SecError(errSecParam
, error
, CFSTR("Why are you doing this?"));
2849 #endif /* USE_KEYSTORE */
2851 backup
= SecServerKeychainCreateBackup(dbt
, client
, keybag
, passcode
, emcs
, error
);
2853 return (backup
!= NULL
);
2860 _SecServerKeychainRestore(CFDataRef backup
, SecurityClient
*client
, CFDataRef keybag
, CFDataRef passcode
, CFErrorRef
*error
) {
2861 if (backup
== NULL
|| keybag
== NULL
)
2862 return SecError(errSecParam
, error
, CFSTR("backup or keybag missing"));
2864 __block
bool ok
= true;
2865 ok
&= kc_with_dbt(true, error
, ^bool (SecDbConnectionRef dbconn
) {
2866 return SecServerKeychainRestore(dbconn
, client
, backup
, keybag
, passcode
, error
);
2870 SecKeychainChanged();
2877 _SecServerBackupCopyUUID(CFDataRef data
, CFErrorRef
*error
)
2879 CFStringRef uuid
= NULL
;
2880 CFDictionaryRef backup
;
2882 backup
= CFPropertyListCreateWithData(kCFAllocatorDefault
, data
,
2883 kCFPropertyListImmutable
, NULL
,
2885 if (isDictionary(backup
)) {
2886 uuid
= SecServerBackupGetKeybagUUID(backup
, error
);
2890 CFReleaseNull(backup
);
2898 // MARK: SecItemDataSource
2900 // Make sure to call this before any writes to the keychain, so that we fire
2901 // up the engines to monitor manifest changes.
2902 SOSDataSourceFactoryRef
SecItemDataSourceFactoryGetDefault(void) {
2903 return SecItemDataSourceFactoryGetShared(kc_dbhandle(NULL
));
2906 /* AUDIT[securityd]:
2907 args_in (ok) is a caller provided, CFDictionaryRef.
2910 CF_RETURNS_RETAINED CFArrayRef
2911 _SecServerKeychainSyncUpdateMessage(CFDictionaryRef updates
, CFErrorRef
*error
) {
2912 // This never fails, trust us!
2913 return SOSCCHandleUpdateMessage(updates
);
2917 // Truthiness in the cloud backup/restore support.
2920 static CFDictionaryRef
2921 _SecServerCopyTruthInTheCloud(CFDataRef keybag
, CFDataRef password
,
2922 CFDictionaryRef backup
, CFErrorRef
*error
)
2924 SOSManifestRef mold
= NULL
, mnow
= NULL
, mdelete
= NULL
, madd
= NULL
;
2925 __block CFMutableDictionaryRef backup_new
= NULL
;
2926 keybag_handle_t bag_handle
;
2927 if (!ks_open_keybag(keybag
, password
, &bag_handle
, error
))
2930 // We need to have a datasource singleton for protection domain
2931 // kSecAttrAccessibleWhenUnlocked and keep a single shared engine
2932 // instance around which we create in the datasource constructor as well.
2933 SOSDataSourceFactoryRef dsf
= SecItemDataSourceFactoryGetDefault();
2934 SOSDataSourceRef ds
= SOSDataSourceFactoryCreateDataSource(dsf
, kSecAttrAccessibleWhenUnlocked
, error
);
2936 backup_new
= backup
? CFDictionaryCreateMutableCopy(kCFAllocatorDefault
, 0, backup
) : CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2937 mold
= SOSCreateManifestWithBackup(backup
, error
);
2938 SOSEngineRef engine
= SOSDataSourceGetSharedEngine(ds
, error
);
2939 mnow
= SOSEngineCopyManifest(engine
, NULL
);
2941 mnow
= SOSDataSourceCopyManifestWithViewNameSet(ds
, SOSViewsGetV0ViewSet(), error
);
2944 CFReleaseNull(backup_new
);
2945 secerror("failed to obtain manifest for keychain: %@", error
? *error
: NULL
);
2947 SOSManifestDiff(mold
, mnow
, &mdelete
, &madd
, error
);
2950 // Delete everything from the new_backup that is no longer in the datasource according to the datasources manifest.
2951 SOSManifestForEach(mdelete
, ^(CFDataRef digest_data
, bool *stop
) {
2952 CFStringRef deleted_item_key
= CFDataCopyHexString(digest_data
);
2953 CFDictionaryRemoveValue(backup_new
, deleted_item_key
);
2954 CFRelease(deleted_item_key
);
2957 CFMutableArrayRef changes
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
2958 SOSDataSourceForEachObject(ds
, NULL
, madd
, error
, ^void(CFDataRef digest
, SOSObjectRef object
, bool *stop
) {
2959 CFErrorRef localError
= NULL
;
2960 CFDataRef digest_data
= NULL
;
2961 CFTypeRef value
= NULL
;
2963 // Key in our manifest can't be found in db, remove it from our manifest
2964 SOSChangesAppendDelete(changes
, digest
);
2965 } else if (!(digest_data
= SOSObjectCopyDigest(ds
, object
, &localError
))
2966 || !(value
= SOSObjectCopyBackup(ds
, object
, bag_handle
, &localError
))) {
2967 if (SecErrorGetOSStatus(localError
) == errSecDecode
) {
2968 // Ignore decode errors, pretend the objects aren't there
2969 CFRelease(localError
);
2970 // Object undecodable, remove it from our manifest
2971 SOSChangesAppendDelete(changes
, digest
);
2973 // Stop iterating and propagate out all other errors.
2975 *error
= localError
;
2976 CFReleaseNull(backup_new
);
2979 // TODO: Should we skip tombstones here?
2980 CFStringRef key
= CFDataCopyHexString(digest_data
);
2981 CFDictionarySetValue(backup_new
, key
, value
);
2984 CFReleaseSafe(digest_data
);
2985 CFReleaseSafe(value
);
2986 }) || CFReleaseNull(backup_new
);
2988 if (CFArrayGetCount(changes
)) {
2989 if (!SOSEngineUpdateChanges(engine
, kSOSDataSourceSOSTransaction
, changes
, error
)) {
2990 CFReleaseNull(backup_new
);
2993 CFReleaseSafe(changes
);
2995 SOSDataSourceRelease(ds
, error
) || CFReleaseNull(backup_new
);
2998 CFReleaseSafe(mold
);
2999 CFReleaseSafe(mnow
);
3000 CFReleaseSafe(madd
);
3001 CFReleaseSafe(mdelete
);
3002 ks_close_keybag(bag_handle
, error
) || CFReleaseNull(backup_new
);
3008 _SecServerRestoreTruthInTheCloud(CFDataRef keybag
, CFDataRef password
, CFDictionaryRef backup_in
, CFErrorRef
*error
) {
3009 __block
bool ok
= true;
3010 keybag_handle_t bag_handle
;
3011 if (!ks_open_keybag(keybag
, password
, &bag_handle
, error
))
3014 SOSManifestRef mbackup
= SOSCreateManifestWithBackup(backup_in
, error
);
3016 SOSDataSourceFactoryRef dsf
= SecItemDataSourceFactoryGetDefault();
3017 SOSDataSourceRef ds
= SOSDataSourceFactoryCreateDataSource(dsf
, kSecAttrAccessibleWhenUnlocked
, error
);
3018 ok
&= ds
&& SOSDataSourceWith(ds
, error
, ^(SOSTransactionRef txn
, bool *commit
) {
3019 SOSManifestRef mnow
= SOSDataSourceCopyManifestWithViewNameSet(ds
, SOSViewsGetV0BackupViewSet(), error
);
3020 SOSManifestRef mdelete
= NULL
, madd
= NULL
;
3021 SOSManifestDiff(mnow
, mbackup
, &mdelete
, &madd
, error
);
3023 // Don't delete everything in datasource not in backup.
3025 // Add items from the backup
3026 SOSManifestForEach(madd
, ^void(CFDataRef e
, bool *stop
) {
3027 CFDictionaryRef item
= NULL
;
3028 CFStringRef sha1
= CFDataCopyHexString(e
);
3030 item
= CFDictionaryGetValue(backup_in
, sha1
);
3034 CFErrorRef localError
= NULL
;
3036 if (!SOSObjectRestoreObject(ds
, txn
, bag_handle
, item
, &localError
)) {
3037 OSStatus status
= SecErrorGetOSStatus(localError
);
3038 if (status
== errSecDuplicateItem
) {
3039 // Log and ignore duplicate item errors during restore
3040 secnotice("titc", "restore %@ not replacing existing item", item
);
3041 } else if (status
== errSecDecode
) {
3042 // Log and ignore corrupted item errors during restore
3043 secnotice("titc", "restore %@ skipping corrupted item %@", item
, localError
);
3045 if (status
== errSecInteractionNotAllowed
)
3047 // Propagate the first other error upwards (causing the restore to fail).
3048 secerror("restore %@ failed %@", item
, localError
);
3050 if (error
&& !*error
) {
3051 *error
= localError
;
3055 CFReleaseSafe(localError
);
3059 ok
&= SOSDataSourceRelease(ds
, error
);
3060 CFReleaseNull(mdelete
);
3061 CFReleaseNull(madd
);
3062 CFReleaseNull(mnow
);
3067 ok
&= ks_close_keybag(bag_handle
, error
);
3073 CF_RETURNS_RETAINED CFDictionaryRef
3074 _SecServerBackupSyncable(CFDictionaryRef backup
, CFDataRef keybag
, CFDataRef password
, CFErrorRef
*error
) {
3075 require_action_quiet(isData(keybag
), errOut
, SecError(errSecParam
, error
, CFSTR("keybag %@ not a data"), keybag
));
3076 require_action_quiet(!backup
|| isDictionary(backup
), errOut
, SecError(errSecParam
, error
, CFSTR("backup %@ not a dictionary"), backup
));
3077 require_action_quiet(!password
|| isData(password
), errOut
, SecError(errSecParam
, error
, CFSTR("password %@ not a data"), password
));
3079 return _SecServerCopyTruthInTheCloud(keybag
, password
, backup
, error
);
3086 _SecServerRestoreSyncable(CFDictionaryRef backup
, CFDataRef keybag
, CFDataRef password
, CFErrorRef
*error
) {
3088 require_action_quiet(isData(keybag
), errOut
, ok
= SecError(errSecParam
, error
, CFSTR("keybag %@ not a data"), keybag
));
3089 require_action_quiet(isDictionary(backup
), errOut
, ok
= SecError(errSecParam
, error
, CFSTR("backup %@ not a dictionary"), backup
));
3092 require_action_quiet(isData(password
), errOut
, ok
= SecError(errSecParam
, error
, CFSTR("password not a data")));
3095 ok
= _SecServerRestoreTruthInTheCloud(keybag
, password
, backup
, error
);
3101 bool _SecServerRollKeysGlue(bool force
, CFErrorRef
*error
) {
3102 return _SecServerRollKeys(force
, NULL
, error
);
3106 bool _SecServerRollKeys(bool force
, SecurityClient
*client
, CFErrorRef
*error
) {
3108 uint32_t keystore_generation_status
= 0;
3109 if (aks_generation(KEYBAG_DEVICE
, generation_noop
, &keystore_generation_status
))
3111 uint32_t current_generation
= keystore_generation_status
& generation_current
;
3113 return kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
3114 bool up_to_date
= s3dl_dbt_keys_current(dbt
, current_generation
, NULL
);
3116 if (force
&& !up_to_date
) {
3117 up_to_date
= s3dl_dbt_update_keys(dbt
, client
, error
);
3119 secerror("Completed roll keys.");
3120 up_to_date
= s3dl_dbt_keys_current(dbt
, current_generation
, NULL
);
3123 secerror("Failed to roll keys.");
3133 InitialSyncItems(CFMutableArrayRef items
, bool limitToCurrent
, CFStringRef agrp
, CFStringRef svce
, const SecDbClass
*qclass
, CFErrorRef
*error
)
3135 bool result
= false;
3138 q
= query_create(qclass
, NULL
, NULL
, error
);
3141 q
->q_return_type
= kSecReturnDataMask
| kSecReturnAttributesMask
;
3142 q
->q_limit
= kSecMatchUnlimited
;
3143 q
->q_keybag
= KEYBAG_DEVICE
;
3145 query_add_attribute(kSecAttrAccessGroup
, agrp
, q
);
3146 query_add_attribute(kSecAttrSynchronizable
, kCFBooleanTrue
, q
);
3147 query_add_attribute(kSecAttrTombstone
, kCFBooleanFalse
, q
);
3149 query_add_attribute(kSecAttrService
, svce
, q
);
3151 result
= kc_with_dbt(false, error
, ^(SecDbConnectionRef dbt
) {
3152 return kc_transaction(dbt
, error
, ^{
3153 CFErrorRef error2
= NULL
;
3155 SecDbItemSelect(q
, dbt
, &error2
, NULL
, ^bool(const SecDbAttr
*attr
) {
3156 return CFDictionaryGetValue(q
->q_item
, attr
->name
);
3157 }, NULL
, NULL
, ^(SecDbItemRef item
, bool *stop
) {
3158 CFErrorRef error3
= NULL
;
3159 secinfo("InitialSyncItems", "Copy item");
3161 CFMutableDictionaryRef attrs
= SecDbItemCopyPListWithMask(item
, kSecDbSyncFlag
, &error3
);
3164 CFStringRef itemvwht
= CFDictionaryGetValue(attrs
, kSecAttrSyncViewHint
);
3166 * Saying its a SOS viewhint is really not the right answer post Triangle
3168 if (isString(itemvwht
) && !SOSViewInSOSSystem(itemvwht
)) {
3172 * Here we encode how PCS stores identities so that we only copy the
3173 * current identites for performance reasons.
3175 if (limitToCurrent
) {
3176 enum { PCS_CURRENT_IDENTITY_OFFSET
= 0x10000 };
3179 CFNumberRef type
= CFDictionaryGetValue(attrs
, kSecAttrType
);
3180 if (!isNumber(type
)) {
3181 // still allow this case since its not a service identity ??
3182 } else if (!CFNumberGetValue(type
, kCFNumberSInt32Type
, &s32
)) {
3184 } else if ((s32
& PCS_CURRENT_IDENTITY_OFFSET
) == 0) {
3189 CFDictionaryAddValue(attrs
, kSecClass
, SecDbItemGetClass(item
)->name
);
3190 CFArrayAppendValue(items
, attrs
);
3193 CFReleaseNull(attrs
);
3195 CFReleaseNull(error3
);
3197 CFReleaseNull(error2
);
3205 query_destroy(q
, NULL
);
3210 _SecServerCopyInitialSyncCredentials(uint32_t flags
, CFErrorRef
*error
)
3212 CFMutableArrayRef items
= CFArrayCreateMutableForCFTypes(NULL
);
3214 if (flags
& SecServerInitialSyncCredentialFlagTLK
) {
3215 require_action(InitialSyncItems(items
, false, CFSTR("com.apple.security.ckks"), NULL
, inet_class(), error
), fail
,
3216 secerror("failed to collect CKKS-inet keys: %@", error
? *error
: NULL
));
3218 if (flags
& SecServerInitialSyncCredentialFlagPCS
) {
3219 bool onlyCurrent
= !(flags
& SecServerInitialSyncCredentialFlagPCSNonCurrent
);
3221 require_action(InitialSyncItems(items
, false, CFSTR("com.apple.ProtectedCloudStorage"), NULL
, genp_class(), error
), fail
,
3222 secerror("failed to collect PCS-genp keys: %@", error
? *error
: NULL
));
3223 require_action(InitialSyncItems(items
, onlyCurrent
, CFSTR("com.apple.ProtectedCloudStorage"), NULL
, inet_class(), error
), fail
,
3224 secerror("failed to collect PCS-inet keys: %@", error
? *error
: NULL
));
3226 if (flags
& SecServerInitialSyncCredentialFlagBluetoothMigration
) {
3227 require_action(InitialSyncItems(items
, false, CFSTR("com.apple.nanoregistry.migration"), NULL
, genp_class(), error
), fail
,
3228 secerror("failed to collect com.apple.nanoregistry.migration-genp item: %@", error
? *error
: NULL
));
3229 require_action(InitialSyncItems(items
, false, CFSTR("com.apple.bluetooth"), CFSTR("BluetoothLESync"), genp_class(), error
), fail
,
3230 secerror("failed to collect com.apple.bluetooth-genp item: %@", error
? *error
: NULL
));
3239 _SecServerImportInitialSyncCredentials(CFArrayRef array
, CFErrorRef
*error
)
3241 return kc_with_dbt(true, error
, ^bool(SecDbConnectionRef dbt
) {
3242 return kc_transaction(dbt
, error
, ^bool(void){
3243 CFIndex n
, count
= CFArrayGetCount(array
);
3245 secinfo("ImportInitialSyncItems", "Importing %d items", (int)count
);
3247 for (n
= 0; n
< count
; n
++) {
3248 CFErrorRef cferror
= NULL
;
3250 CFDictionaryRef item
= CFArrayGetValueAtIndex(array
, n
);
3251 if (!isDictionary(item
))
3254 CFStringRef className
= CFDictionaryGetValue(item
, kSecClass
);
3255 if (className
== NULL
) {
3256 secinfo("ImportInitialSyncItems", "Item w/o class");
3260 const SecDbClass
*cls
= kc_class_with_name(className
);
3262 secinfo("ImportInitialSyncItems", "Item with unknown class: %@", className
);
3266 SecDbItemRef dbi
= SecDbItemCreateWithAttributes(NULL
, cls
, item
, KEYBAG_DEVICE
, &cferror
);
3268 secinfo("ImportInitialSyncItems", "Item creation failed with: %@", cferror
);
3269 CFReleaseNull(cferror
);
3273 if (!SecDbItemSetSyncable(dbi
, true, &cferror
)) {
3274 secinfo("ImportInitialSyncItems", "Failed to set sync=1: %@ for item %@", cferror
, dbi
);
3275 CFReleaseNull(cferror
);
3280 if (!SecDbItemInsert(dbi
, dbt
, &cferror
)) {
3281 secinfo("ImportInitialSyncItems", "Item store failed with: %@: %@", cferror
, dbi
);
3282 CFReleaseNull(cferror
);
3296 * Sync bubble migration code
3299 struct SyncBubbleRule
{
3300 CFStringRef attribute
;
3305 TransmogrifyItemsToSyncBubble(SecurityClient
*client
, uid_t uid
,
3308 const SecDbClass
*qclass
,
3309 struct SyncBubbleRule
*items
, CFIndex nItems
,
3312 CFMutableDictionaryRef updateAttributes
= NULL
;
3313 CFDataRef syncBubbleView
= NULL
;
3314 CFDataRef activeUserView
= NULL
;
3319 syncBubbleView
= SecMUSRCreateSyncBubbleUserUUID(uid
);
3320 require(syncBubbleView
, fail
);
3322 activeUserView
= SecMUSRCreateActiveUserUUID(uid
);
3323 require(activeUserView
, fail
);
3326 if ((onlyDelete
&& !copyToo
) || !onlyDelete
) {
3329 * Clean out items first
3332 secnotice("syncbubble", "cleaning out old items");
3334 q
= query_create(qclass
, NULL
, NULL
, error
);
3337 q
->q_limit
= kSecMatchUnlimited
;
3338 q
->q_keybag
= device_keybag_handle
;
3340 for (n
= 0; n
< nItems
; n
++) {
3341 query_add_attribute(items
[n
].attribute
, items
[n
].value
, q
);
3343 q
->q_musrView
= CFRetain(syncBubbleView
);
3344 require(q
->q_musrView
, fail
);
3346 kc_with_dbt(false, error
, ^(SecDbConnectionRef dbt
) {
3347 return kc_transaction(dbt
, error
, ^{
3348 return s3dl_query_delete(dbt
, q
, NULL
, error
);
3352 query_destroy(q
, NULL
);
3357 if (onlyDelete
|| !copyToo
) {
3358 secnotice("syncbubble", "skip migration of items");
3361 * Copy over items from EMCS to sync bubble
3364 secnotice("syncbubble", "migrating sync bubble items");
3366 q
= query_create(qclass
, NULL
, NULL
, error
);
3369 q
->q_return_type
= kSecReturnDataMask
| kSecReturnAttributesMask
;
3370 q
->q_limit
= kSecMatchUnlimited
;
3371 q
->q_keybag
= device_keybag_handle
; /* XXX change to session key bag when it exists */
3373 for (n
= 0; n
< nItems
; n
++) {
3374 query_add_or_attribute(items
[n
].attribute
, items
[n
].value
, q
);
3376 query_add_or_attribute(CFSTR("musr"), activeUserView
, q
);
3377 q
->q_musrView
= CFRetain(activeUserView
);
3379 updateAttributes
= CFDictionaryCreateMutableForCFTypes(NULL
);
3380 require(updateAttributes
, fail
);
3382 CFDictionarySetValue(updateAttributes
, CFSTR("musr"), syncBubbleView
); /* XXX should use kSecAttrMultiUser */
3385 kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
3386 return kc_transaction(dbt
, error
, ^{
3387 CFErrorRef error2
= NULL
;
3389 SecDbItemSelect(q
, dbt
, &error2
, NULL
, ^bool(const SecDbAttr
*attr
) {
3390 return CFDictionaryGetValue(q
->q_item
, attr
->name
);
3391 }, NULL
, NULL
, ^(SecDbItemRef item
, bool *stop
) {
3392 CFErrorRef error3
= NULL
;
3393 secinfo("syncbubble", "migrating item");
3395 SecDbItemRef new_item
= SecDbItemCopyWithUpdates(item
, updateAttributes
, NULL
);
3396 if (new_item
== NULL
)
3399 SecDbItemClearRowId(new_item
, NULL
);
3401 if (!SecDbItemSetKeybag(new_item
, device_keybag_handle
, NULL
)) {
3402 CFRelease(new_item
);
3406 if (!SecDbItemInsert(new_item
, dbt
, &error3
)) {
3407 secnotice("syncbubble", "migration failed with %@ for item %@", error3
, new_item
);
3409 CFRelease(new_item
);
3410 CFReleaseNull(error3
);
3412 CFReleaseNull(error2
);
3421 CFReleaseNull(syncBubbleView
);
3422 CFReleaseNull(activeUserView
);
3423 CFReleaseNull(updateAttributes
);
3425 query_destroy(q
, NULL
);
3430 static struct SyncBubbleRule PCSItems
[] = {
3432 .attribute
= CFSTR("agrp"),
3433 .value
= CFSTR("com.apple.ProtectedCloudStorage"),
3436 static struct SyncBubbleRule NSURLSesssiond
[] = {
3438 .attribute
= CFSTR("agrp"),
3439 .value
= CFSTR("com.apple.nsurlsessiond"),
3442 static struct SyncBubbleRule AccountsdItems
[] = {
3444 .attribute
= CFSTR("svce"),
3445 .value
= CFSTR("com.apple.account.AppleAccount.token"),
3448 .attribute
= CFSTR("svce"),
3449 .value
= CFSTR("com.apple.account.AppleAccount.password"),
3452 .attribute
= CFSTR("svce"),
3453 .value
= CFSTR("com.apple.account.AppleAccount.rpassword"),
3456 .attribute
= CFSTR("svce"),
3457 .value
= CFSTR("com.apple.account.idms.token"),
3460 .attribute
= CFSTR("svce"),
3461 .value
= CFSTR("com.apple.account.idms.continuation-key"),
3464 .attribute
= CFSTR("svce"),
3465 .value
= CFSTR("com.apple.account.CloudKit.token"),
3469 static struct SyncBubbleRule MobileMailItems
[] = {
3471 .attribute
= CFSTR("svce"),
3472 .value
= CFSTR("com.apple.account.IMAP.password"),
3475 .attribute
= CFSTR("svce"),
3476 .value
= CFSTR("com.apple.account.SMTP.password"),
3479 .attribute
= CFSTR("svce"),
3480 .value
= CFSTR("com.apple.account.Exchange.password"),
3483 .attribute
= CFSTR("svce"),
3484 .value
= CFSTR("com.apple.account.Hotmail.password"),
3487 .attribute
= CFSTR("svce"),
3488 .value
= CFSTR("com.apple.account.Google.password"),
3491 .attribute
= CFSTR("svce"),
3492 .value
= CFSTR("com.apple.account.Google.oauth-token"),
3495 .attribute
= CFSTR("svce"),
3496 .value
= CFSTR("com.apple.account.Google.oath-refresh-token"),
3499 .attribute
= CFSTR("svce"),
3500 .value
= CFSTR("com.apple.account.Yahoo.password"),
3503 .attribute
= CFSTR("svce"),
3504 .value
= CFSTR("com.apple.account.Yahoo.oauth-token"),
3507 .attribute
= CFSTR("svce"),
3508 .value
= CFSTR("com.apple.account.Yahoo.oauth-token-nosync"),
3511 .attribute
= CFSTR("svce"),
3512 .value
= CFSTR("com.apple.account.Yahoo.oath-refresh-token"),
3515 .attribute
= CFSTR("svce"),
3516 .value
= CFSTR("com.apple.account.IMAPNotes.password"),
3519 .attribute
= CFSTR("svce"),
3520 .value
= CFSTR("com.apple.account.IMAPMail.password"),
3523 .attribute
= CFSTR("svce"),
3524 .value
= CFSTR("com.apple.account.126.password"),
3527 .attribute
= CFSTR("svce"),
3528 .value
= CFSTR("com.apple.account.163.password"),
3531 .attribute
= CFSTR("svce"),
3532 .value
= CFSTR("com.apple.account.aol.password"),
3537 ArrayContains(CFArrayRef array
, CFStringRef service
)
3539 return CFArrayContainsValue(array
, CFRangeMake(0, CFArrayGetCount(array
)), service
);
3543 _SecServerTransmogrifyToSyncBubble(CFArrayRef services
, uid_t uid
, SecurityClient
*client
, CFErrorRef
*error
)
3545 bool copyCloudAuthToken
= false;
3546 bool copyMobileMail
= false;
3548 bool copyPCS
= false;
3549 bool onlyDelete
= false;
3550 bool copyNSURLSesssion
= false;
3552 if (!client
->inMultiUser
)
3555 secnotice("syncbubble", "migration for uid %d uid for services %@", (int)uid
, services
);
3557 #if TARGET_OS_SIMULATOR
3560 if (uid
!= (uid_t
)client
->activeUser
)
3563 #error "no sync bubble on other platforms"
3567 * First select that services to copy/delete
3570 if (ArrayContains(services
, CFSTR("com.apple.bird.usermanager.sync"))
3571 || ArrayContains(services
, CFSTR("com.apple.cloudphotod.sync"))
3572 || ArrayContains(services
, CFSTR("com.apple.cloudphotod.syncstakeholder"))
3573 || ArrayContains(services
, CFSTR("com.apple.cloudd.usermanager.sync")))
3575 copyCloudAuthToken
= true;
3579 if (ArrayContains(services
, CFSTR("com.apple.nsurlsessiond.usermanager.sync")))
3581 copyCloudAuthToken
= true;
3582 copyNSURLSesssion
= true;
3585 if (ArrayContains(services
, CFSTR("com.apple.syncdefaultsd.usermanager.sync"))) {
3586 copyCloudAuthToken
= true;
3588 if (ArrayContains(services
, CFSTR("com.apple.mailq.sync")) || ArrayContains(services
, CFSTR("com.apple.mailq.sync.xpc"))) {
3589 copyCloudAuthToken
= true;
3590 copyMobileMail
= true;
3595 * The actually copy/delete the items selected
3598 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyPCS
, inet_class(), PCSItems
, sizeof(PCSItems
)/sizeof(PCSItems
[0]), error
);
3600 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyPCS
, genp_class(), PCSItems
, sizeof(PCSItems
)/sizeof(PCSItems
[0]), error
);
3604 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyMobileMail
, genp_class(), MobileMailItems
, sizeof(MobileMailItems
)/sizeof(MobileMailItems
[0]), error
);
3608 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyCloudAuthToken
, genp_class(), AccountsdItems
, sizeof(AccountsdItems
)/sizeof(AccountsdItems
[0]), error
);
3612 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyNSURLSesssion
, inet_class(), NSURLSesssiond
, sizeof(NSURLSesssiond
)/sizeof(NSURLSesssiond
[0]), error
);
3620 * Migrate from user keychain to system keychain when switching to edu mode
3624 _SecServerTransmogrifyToSystemKeychain(SecurityClient
*client
, CFErrorRef
*error
)
3626 __block
bool ok
= true;
3629 * we are not in multi user yet, about to switch, otherwise we would
3630 * check that for client->inMultiuser here
3633 kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
3634 return kc_transaction(dbt
, error
, ^{
3635 CFDataRef systemUUID
= SecMUSRGetSystemKeychainUUID();
3637 const SecDbSchema
*newSchema
= current_schema();
3638 SecDbClass
const *const *kcClass
;
3640 for (kcClass
= newSchema
->classes
; *kcClass
!= NULL
; kcClass
++) {
3641 CFErrorRef localError
= NULL
;
3644 if (!((*kcClass
)->itemclass
)) {
3648 q
= query_create(*kcClass
, SecMUSRGetSingleUserKeychainUUID(), NULL
, error
);
3652 ok
&= SecDbItemSelect(q
, dbt
, error
, ^bool(const SecDbAttr
*attr
) {
3653 return (attr
->flags
& kSecDbInFlag
) != 0;
3654 }, ^bool(const SecDbAttr
*attr
) {
3655 // No filtering please.
3657 }, ^bool(CFMutableStringRef sql
, bool *needWhere
) {
3658 SecDbAppendWhereOrAnd(sql
, needWhere
);
3659 CFStringAppendFormat(sql
, NULL
, CFSTR("musr = ?"));
3661 }, ^bool(sqlite3_stmt
*stmt
, int col
) {
3662 return SecDbBindObject(stmt
, col
++, SecMUSRGetSingleUserKeychainUUID(), error
);
3663 }, ^(SecDbItemRef item
, bool *stop
) {
3664 CFErrorRef localError
= NULL
;
3666 if (!SecDbItemSetValueWithName(item
, kSecAttrMultiUser
, systemUUID
, &localError
)) {
3667 secerror("item: %@ update musr to system failed: %@", item
, localError
);
3672 if (!SecDbItemDoUpdate(item
, item
, dbt
, &localError
, ^bool (const SecDbAttr
*attr
) {
3673 return attr
->kind
== kSecDbRowIdAttr
;
3675 secerror("item: %@ insert during UPDATE: %@", item
, localError
);
3681 SecErrorPropagate(localError
, error
);
3685 query_destroy(q
, &localError
);
3696 * Delete account from local usage
3700 _SecServerDeleteMUSERViews(SecurityClient
*client
, uid_t uid
, CFErrorRef
*error
)
3702 return kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
3703 CFDataRef musrView
= NULL
, syncBubbleView
= NULL
;
3706 syncBubbleView
= SecMUSRCreateSyncBubbleUserUUID(uid
);
3707 require(syncBubbleView
, fail
);
3709 musrView
= SecMUSRCreateActiveUserUUID(uid
);
3710 require(musrView
, fail
);
3712 require(ok
= SecServerDeleteAllForUser(dbt
, syncBubbleView
, false, error
), fail
);
3713 require(ok
= SecServerDeleteAllForUser(dbt
, musrView
, false, error
), fail
);
3716 CFReleaseNull(syncBubbleView
);
3717 CFReleaseNull(musrView
);
3723 #endif /* TARGET_OS_IOS */
3726 _SecServerGetKeyStats(const SecDbClass
*qclass
,
3727 struct _SecServerKeyStats
*stats
)
3729 __block CFErrorRef error
= NULL
;
3732 Query
*q
= query_create(qclass
, NULL
, NULL
, &error
);
3735 q
->q_return_type
= kSecReturnDataMask
| kSecReturnAttributesMask
;
3736 q
->q_limit
= kSecMatchUnlimited
;
3737 q
->q_keybag
= KEYBAG_DEVICE
;
3738 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleWhenUnlocked
, q
);
3739 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleAfterFirstUnlock
, q
);
3740 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleAlways
, q
);
3741 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleWhenUnlockedThisDeviceOnly
, q
);
3742 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
, q
);
3743 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleAlwaysThisDeviceOnly
, q
);
3744 query_add_attribute(kSecAttrTombstone
, kCFBooleanFalse
, q
);
3746 kc_with_dbt(false, &error
, ^(SecDbConnectionRef dbconn
) {
3747 CFErrorRef error2
= NULL
;
3748 __block CFIndex totalSize
= 0;
3749 stats
->maxDataSize
= 0;
3751 SecDbItemSelect(q
, dbconn
, &error2
, NULL
, ^bool(const SecDbAttr
*attr
) {
3752 return CFDictionaryContainsKey(q
->q_item
, attr
->name
);
3753 }, NULL
, NULL
, ^(SecDbItemRef item
, bool *stop
) {
3754 CFErrorRef error3
= NULL
;
3755 CFDataRef data
= SecDbItemGetValue(item
, &v6v_Data
, &error3
);
3757 CFIndex size
= CFDataGetLength(data
);
3758 if (size
> stats
->maxDataSize
)
3759 stats
->maxDataSize
= size
;
3763 CFReleaseNull(error3
);
3765 CFReleaseNull(error2
);
3767 stats
->averageSize
= totalSize
/ stats
->items
;
3776 CFReleaseNull(error
);
3778 query_destroy(q
, NULL
);
3782 CFArrayRef
_SecItemCopyParentCertificates(CFDataRef normalizedIssuer
, CFArrayRef accessGroups
, CFErrorRef
*error
) {
3783 const void *keys
[] = {
3790 kSecClassCertificate
,
3795 CFDictionaryRef query
= CFDictionaryCreate(NULL
, keys
, values
, 4,
3797 CFTypeRef results
= NULL
;
3798 SecurityClient client
= {
3800 .accessGroups
= accessGroups
,
3801 .allowSystemKeychain
= true,
3802 .allowSyncBubbleKeychain
= false,
3803 .isNetworkExtension
= false,
3806 (void)_SecItemCopyMatching(query
, &client
, &results
, error
);
3811 bool _SecItemCertificateExists(CFDataRef normalizedIssuer
, CFDataRef serialNumber
, CFArrayRef accessGroups
, CFErrorRef
*error
) {
3812 const void *keys
[] = {
3816 kSecAttrSerialNumber
3819 kSecClassCertificate
,
3824 SecurityClient client
= {
3826 .accessGroups
= accessGroups
,
3827 .allowSystemKeychain
= true,
3828 .allowSyncBubbleKeychain
= false,
3829 .isNetworkExtension
= false,
3831 CFDictionaryRef query
= CFDictionaryCreate(NULL
, keys
, values
, 4, NULL
, NULL
);
3832 CFTypeRef results
= NULL
;
3833 bool ok
= _SecItemCopyMatching(query
, &client
, &results
, error
);
3834 CFReleaseSafe(query
);
3835 CFReleaseSafe(results
);