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
32 #undef SECUREOBJECTSYNC
33 #undef SHAREDWEBCREDENTIALS
36 #include <securityd/SecItemServer.h>
39 #include <securityd/SecItemDataSource.h>
40 #include <securityd/SecItemDb.h>
41 #include <securityd/SecItemSchema.h>
42 #include <utilities/SecDb.h>
43 #include <securityd/SecDbKeychainItem.h>
44 #include <securityd/SOSCloudCircleServer.h>
45 #include <Security/SecBasePriv.h>
46 #include <Security/SecItemPriv.h>
47 #include <Security/SecItemInternal.h>
48 #include <securityd/SecDbBackupManager.h>
49 #import "keychain/SecureObjectSync/SOSChangeTracker.h"
50 #include "keychain/SecureObjectSync/SOSDigestVector.h"
51 #include "keychain/SecureObjectSync/SOSEngine.h"
52 #include <Security/SecureObjectSync/SOSViews.h>
53 #include <Security/SecTrustPriv.h>
54 #include <Security/SecTrustInternal.h>
55 #include <Security/SecCertificatePriv.h>
56 #include <Security/SecEntitlements.h>
57 #include <Security/SecSignpost.h>
59 #include <keychain/ckks/CKKS.h>
60 #import "keychain/ot/OT.h"
61 #import "keychain/ot/OTConstants.h"
62 #import "keychain/escrowrequest/EscrowRequestServerHelpers.h"
66 #if __has_include(<MobileKeyBag/MobileKeyBag.h>)
67 #include <MobileKeyBag/MobileKeyBag.h>
69 #include "OSX/utilities/SecAKSWrappers.h"
72 #if __has_include(<Kernel/IOKit/crypto/AppleKeyStoreDefs.h>)
73 #include <Kernel/IOKit/crypto/AppleKeyStoreDefs.h>
76 #include <IOKit/IOReturn.h>
80 #if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
81 #include <utilities/SecADWrapper.h>
86 #include <utilities/array_size.h>
87 #include <utilities/SecFileLocations.h>
88 #include <utilities/SecTrace.h>
89 #include <utilities/SecXPCError.h>
90 #include <utilities/sec_action.h>
91 #include <Security/SecuritydXPC.h>
92 #include "swcagent_client.h"
93 #include "SecPLWrappers.h"
95 #if TARGET_OS_IOS && !TARGET_OS_BRIDGE
96 #include <SharedWebCredentials/SharedWebCredentials.h>
99 #include <os/variant_private.h>
102 #include "Analytics/Clients/LocalKeychainAnalytics.h"
104 /* Changed the name of the keychain changed notification, for testing */
105 static const char *g_keychain_changed_notification
= kSecServerKeychainChangedNotification
;
107 void SecItemServerSetKeychainChangedNotification(const char *notification_name
)
109 g_keychain_changed_notification
= notification_name
;
112 void SecKeychainChanged() {
113 static dispatch_once_t once
;
114 static sec_action_t action
;
116 dispatch_once(&once
, ^{
117 action
= sec_action_create("SecKeychainChanged", 1);
118 sec_action_set_handler(action
, ^{
119 uint32_t result
= notify_post(g_keychain_changed_notification
);
120 if (result
== NOTIFY_STATUS_OK
)
121 secnotice("item", "Sent %s", g_keychain_changed_notification
);
123 secerror("notify_post %s returned: %" PRIu32
, g_keychain_changed_notification
, result
);
127 sec_action_perform(action
);
130 /* Return the current database version in *version. */
131 bool SecKeychainDbGetVersion(SecDbConnectionRef dbt
, int *version
, CFErrorRef
*error
)
133 __block
bool ok
= true;
134 __block CFErrorRef localError
= NULL
;
135 __block
bool found
= false;
138 * First check for the version table itself
141 ok
&= SecDbPrepare(dbt
, CFSTR("SELECT name FROM sqlite_master WHERE type='table' AND name='tversion'"), &localError
, ^(sqlite3_stmt
*stmt
) {
142 ok
= SecDbStep(dbt
, stmt
, NULL
, ^(bool *stop
) {
147 require_action(ok
, out
, SecDbError(SQLITE_CORRUPT
, error
, CFSTR("Failed to read sqlite_master table: %@"), localError
));
149 secnotice("upgr", "no tversion table, will setup a new database: %@", localError
);
155 * Now build up major.minor
158 ok
&= SecDbPrepare(dbt
, CFSTR("SELECT version FROM tversion"), &localError
, ^(sqlite3_stmt
*stmt
) {
159 ok
= SecDbStep(dbt
, stmt
, NULL
, ^(bool *stop
) {
160 *version
= sqlite3_column_int(stmt
, 0);
165 if (ok
&& (*version
& 0xffff) >= 9) {
166 ok
&= SecDbPrepare(dbt
, CFSTR("SELECT minor FROM tversion WHERE version = ?"), &localError
, ^(sqlite3_stmt
*stmt
) {
167 ok
= SecDbBindInt(stmt
, 1, *version
, &localError
) &&
168 SecDbStep(dbt
, stmt
, NULL
, ^(bool *stop
) {
169 int64_t minor
= sqlite3_column_int64(stmt
, 0);
170 *version
|= ((minor
& 0xff) << 8) | ((minor
& 0xff0000) << 8);
177 secnotice("upgr", "database version is: 0x%08x : %d : %@", *version
, ok
, localError
);
178 CFReleaseSafe(localError
);
185 isClassD(SecDbItemRef item
)
187 CFTypeRef accessible
= SecDbItemGetCachedValueWithName(item
, kSecAttrAccessible
);
189 if (CFEqualSafe(accessible
, kSecAttrAccessibleAlwaysPrivate
) || CFEqualSafe(accessible
, kSecAttrAccessibleAlwaysThisDeviceOnlyPrivate
))
194 #if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
197 measureDuration(struct timeval
*start
)
202 gettimeofday(&stop
, NULL
);
204 duration
= (stop
.tv_sec
-start
->tv_sec
) * 1000;
205 duration
+= (stop
.tv_usec
/ 1000) - (start
->tv_usec
/ 1000);
207 return SecBucket2Significant(duration
);
211 measureUpgradePhase1(struct timeval
*start
, bool success
, int64_t itemsMigrated
)
213 int64_t duration
= measureDuration(start
);
216 SecADSetValueForScalarKey(CFSTR("com.apple.keychain.phase1.migrated-items-success"), itemsMigrated
);
217 SecADSetValueForScalarKey(CFSTR("com.apple.keychain.phase1.migrated-time-success"), duration
);
219 SecADSetValueForScalarKey(CFSTR("com.apple.keychain.phase1.migrated-items-fail"), itemsMigrated
);
220 SecADSetValueForScalarKey(CFSTR("com.apple.keychain.phase1.migrated-time-fail"), duration
);
225 measureUpgradePhase2(struct timeval
*start
, int64_t itemsMigrated
)
227 int64_t duration
= measureDuration(start
);
229 SecADSetValueForScalarKey(CFSTR("com.apple.keychain.phase2.migrated-items"), itemsMigrated
);
230 SecADSetValueForScalarKey(CFSTR("com.apple.keychain.phase2.migrated-time"), duration
);
232 #endif /* TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR */
234 static bool DBClassesAreEqual(const SecDbClass
* class1
, const SecDbClass
* class2
)
236 if (CFEqual(class1
->name
, class2
->name
) && class1
->itemclass
== class2
->itemclass
) {
238 const SecDbAttr
* class1Attr
= class1
->attrs
[attrIndex
];
239 const SecDbAttr
* class2Attr
= class2
->attrs
[attrIndex
];
241 while (class1Attr
&& class2Attr
) {
242 if (CFEqual(class1Attr
->name
, class2Attr
->name
) && class1Attr
->kind
== class2Attr
->kind
&& class1Attr
->flags
== class2Attr
->flags
&& class1Attr
->copyValue
== class2Attr
->copyValue
&& class1Attr
->setValue
== class2Attr
->setValue
) {
244 class1Attr
= class1
->attrs
[attrIndex
];
245 class2Attr
= class2
->attrs
[attrIndex
];
252 // if everything has checked out to this point, and we've hit the end of both class's attr list, then they're equal
253 if (class1Attr
== NULL
&& class2Attr
== NULL
) {
261 static bool ShouldRenameTable(const SecDbClass
* class1
, const SecDbClass
* class2
, int oldTableVersion
)
263 return oldTableVersion
< 10 || !DBClassesAreEqual(class1
, class2
);
266 #define SCHEMA_VERSION(schema) ((((schema)->minorVersion) << 8) | ((schema)->majorVersion))
267 #define VERSION_MAJOR(version) ((version) & 0xff)
268 #define VERSION_MINOR(version) (((version) >> 8) & 0xff)
269 #define VERSION_NEW(version) ((version) & 0xffff)
270 #define VERSION_OLD(version) (((version) >> 16) & 0xffff)
272 // Goes through all tables represented by old_schema and tries to migrate all items from them into new (current version) tables.
273 static bool UpgradeSchemaPhase1(SecDbConnectionRef dbt
, const SecDbSchema
*oldSchema
, CFErrorRef
*error
)
275 int oldVersion
= SCHEMA_VERSION(oldSchema
);
276 const SecDbSchema
*newSchema
= current_schema();
277 int newVersion
= SCHEMA_VERSION(newSchema
);
278 __block
bool ok
= true;
279 SecDbClass
const *const *oldClass
;
280 SecDbClass
const *const *newClass
;
281 SecDbQueryRef query
= NULL
;
282 CFMutableStringRef sql
= NULL
;
283 SecDbClass
* renamedOldClass
= NULL
;
284 #if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
285 __block
int64_t itemsMigrated
= 0;
286 struct timeval start
;
288 gettimeofday(&start
, NULL
);
291 // Rename existing tables to names derived from old schema names
292 sql
= CFStringCreateMutable(NULL
, 0);
293 bool oldClassDone
= false;
294 CFMutableArrayRef classIndexesForNewTables
= CFArrayCreateMutable(NULL
, 0, NULL
);
296 for (oldClass
= oldSchema
->classes
, newClass
= newSchema
->classes
;
297 *newClass
!= NULL
; classIndex
++, oldClass
++, newClass
++) {
299 oldClassDone
|= (*oldClass
) == NULL
; // Check if the new schema has more tables than the old
302 if (!oldClassDone
&& !CFEqual((*oldClass
)->name
, (*newClass
)->name
) && ShouldRenameTable(*oldClass
, *newClass
, oldSchema
->majorVersion
)) {
303 CFStringAppendFormat(sql
, NULL
, CFSTR("ALTER TABLE %@ RENAME TO %@_old;"), (*newClass
)->name
, (*oldClass
)->name
);
304 CFArrayAppendValue(classIndexesForNewTables
, (void*)(long)classIndex
);
306 } else if (!oldClassDone
&& !DBClassesAreEqual(*oldClass
, *newClass
)) {
307 CFStringAppendFormat(sql
, NULL
, CFSTR("ALTER TABLE %@ RENAME TO %@_old;"), (*newClass
)->name
, (*oldClass
)->name
);
308 CFArrayAppendValue(classIndexesForNewTables
, (void*)(long)classIndex
);
311 if(oldClassDone
&& *newClass
) {
312 // These should be no-ops, unless you're upgrading a previously-upgraded database with an invalid version number
313 CFStringAppendFormat(sql
, NULL
, CFSTR("DROP TABLE IF EXISTS %@;"), (*newClass
)->name
);
315 if (classIndexesForNewTables
) {
316 CFArrayAppendValue(classIndexesForNewTables
, (void*)(long)classIndex
);
321 if(CFStringGetLength(sql
) > 0) {
322 require_action_quiet(ok
&= SecDbExec(dbt
, sql
, error
), out
,
323 LKAReportKeychainUpgradeOutcomeWithError(oldVersion
, newVersion
, LKAKeychainUpgradeOutcomePhase1AlterTables
, error
? *error
: NULL
));
327 // Drop indices that that new schemas will use
328 sql
= CFStringCreateMutable(NULL
, 0);
329 for (newClass
= newSchema
->classes
; *newClass
!= NULL
; newClass
++) {
330 SecDbForEachAttrWithMask((*newClass
), desc
, kSecDbIndexFlag
| kSecDbInFlag
) {
331 CFStringAppendFormat(sql
, 0, CFSTR("DROP INDEX IF EXISTS %@%@;"), (*newClass
)->name
, desc
->name
);
332 if (desc
->kind
== kSecDbSyncAttr
) {
333 CFStringAppendFormat(sql
, 0, CFSTR("DROP INDEX IF EXISTS %@%@0;"), (*newClass
)->name
, desc
->name
);
337 require_action_quiet(ok
&= SecDbExec(dbt
, sql
, error
), out
,
338 LKAReportKeychainUpgradeOutcomeWithError(oldVersion
, newVersion
, LKAKeychainUpgradeOutcomePhase1DropIndices
, error
? *error
: NULL
));
341 // Create tables for new schema.
342 require_action_quiet(ok
&= SecItemDbCreateSchema(dbt
, newSchema
, classIndexesForNewTables
, false, error
), out
,
343 LKAReportKeychainUpgradeOutcomeWithError(oldVersion
, newVersion
, LKAKeychainUpgradeOutcomePhase1CreateSchema
, error
? *error
: NULL
));
344 // Go through all classes of current schema to transfer all items to new tables.
345 for (oldClass
= oldSchema
->classes
, newClass
= newSchema
->classes
;
346 *oldClass
!= NULL
&& *newClass
!= NULL
; oldClass
++, newClass
++) {
348 if (CFEqual((*oldClass
)->name
, (*newClass
)->name
) && DBClassesAreEqual(*oldClass
, *newClass
)) {
352 secnotice("upgr", "Upgrading table %@", (*oldClass
)->name
);
354 // Create a new 'old' class with a new 'old' name.
356 SecDbForEachAttr(*oldClass
, attr
) {
359 if(renamedOldClass
) {
360 CFReleaseNull(renamedOldClass
->name
);
361 free(renamedOldClass
);
363 renamedOldClass
= (SecDbClass
*) malloc(sizeof(SecDbClass
) + sizeof(SecDbAttr
*)*(count
+1));
364 renamedOldClass
->name
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("%@_old"), (*oldClass
)->name
);
365 renamedOldClass
->itemclass
= (*oldClass
)->itemclass
;
366 for(; count
>= 0; count
--) {
367 renamedOldClass
->attrs
[count
] = (*oldClass
)->attrs
[count
];
370 // SecDbItemSelect only works for item classes.
371 if((*oldClass
)->itemclass
) {
372 // Prepare query to iterate through all items in cur_class.
374 query_destroy(query
, NULL
);
375 require_quiet(query
= query_create(renamedOldClass
, SecMUSRGetAllViews(), NULL
, error
), out
);
377 ok
&= SecDbItemSelect(query
, dbt
, error
, ^bool(const SecDbAttr
*attr
) {
378 // We are interested in all attributes which are physically present in the DB.
379 return (attr
->flags
& kSecDbInFlag
) != 0;
380 }, ^bool(const SecDbAttr
*attr
) {
381 // No filtering please.
383 }, NULL
, NULL
, ^(SecDbItemRef item
, bool *stop
) {
384 CFErrorRef localError
= NULL
;
386 #if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
389 // Switch item to the new class.
390 item
->class = *newClass
;
392 if (isClassD(item
)) {
394 ok
&= SecDbItemEnsureDecrypted(item
, true, &localError
);
395 require_quiet(ok
, out
);
397 // Delete SHA1 field from the item, so that it is newly recalculated before storing
398 // the item into the new table.
399 require_quiet(ok
&= SecDbItemSetValue(item
, SecDbClassAttrWithKind(item
->class, kSecDbSHA1Attr
, error
),
400 kCFNull
, error
), out
);
402 // Leave item encrypted, do not ever try to decrypt it since it will fail.
403 item
->_edataState
= kSecDbItemAlwaysEncrypted
;
405 // Drop items with kSecAttrAccessGroupToken, as these items should not be there at all. Since agrp attribute
406 // is always stored as cleartext in the DB column, we can always rely on this attribute being present in item->attributes.
407 // <rdar://problem/33401870>
408 if (CFEqualSafe(SecDbItemGetCachedValueWithName(item
, kSecAttrAccessGroup
), kSecAttrAccessGroupToken
) &&
409 SecDbItemGetCachedValueWithName(item
, kSecAttrTokenID
) == NULL
) {
410 secnotice("upgr", "dropping item during schema upgrade due to agrp=com.apple.token: %@", item
);
412 // Insert new item into the new table.
413 if (!SecDbItemInsert(item
, dbt
, &localError
)) {
414 secerror("item: %@ insert during upgrade: %@", item
, localError
);
421 OSStatus status
= SecErrorGetOSStatus(localError
);
424 // continue to upgrade and don't propagate errors for insert failures
425 // that are typical of a single item failure
427 case errSecDuplicateItem
:
430 case errSecInteractionNotAllowed
:
431 case errSecAuthNeeded
:
434 // This does not mean the keychain is hosed, we just can't use it right now
436 case kAKSReturnNotReady
:
437 case kAKSReturnTimeout
:
439 case errSecNotAvailable
:
440 secnotice("upgr", "Bailing in phase 1 because AKS is unavailable: %@", localError
);
443 ok
&= CFErrorPropagate(CFRetainSafe(localError
), error
);
446 CFReleaseSafe(localError
);
453 require_action_quiet(ok
, out
,
454 LKAReportKeychainUpgradeOutcomeWithError(oldVersion
, newVersion
, LKAKeychainUpgradeOutcomePhase1Items
, error
? *error
: NULL
));
456 // This table does not contain secdb items, and must be transferred without using SecDbItemSelect.
457 // For now, this code does not support removing or renaming any columns, or adding any new non-null columns.
459 sql
= CFStringCreateMutable(NULL
, 0);
462 CFMutableStringRef columns
= CFStringCreateMutable(NULL
, 0);
464 SecDbForEachAttr(renamedOldClass
, attr
) {
466 CFStringAppendFormat(columns
, NULL
, CFSTR(","));
468 CFStringAppendFormat(columns
, NULL
, CFSTR("%@"), attr
->name
);
472 CFStringAppendFormat(sql
, NULL
, CFSTR("INSERT OR REPLACE INTO %@ (%@) SELECT %@ FROM %@;"), (*newClass
)->name
, columns
, columns
, renamedOldClass
->name
);
474 CFReleaseNull(columns
);
475 require_action_quiet(ok
&= SecDbExec(dbt
, sql
, error
), out
,
476 LKAReportKeychainUpgradeOutcomeWithError(oldVersion
, newVersion
, LKAKeychainUpgradeOutcomePhase1NonItems
, error
? *error
: NULL
));
480 // Remove old tables from the DB.
482 sql
= CFStringCreateMutable(NULL
, 0);
483 for (oldClass
= oldSchema
->classes
, newClass
= newSchema
->classes
;
484 *oldClass
!= NULL
&& *newClass
!= NULL
; oldClass
++, newClass
++) {
485 if (CFEqual((*oldClass
)->name
, (*newClass
)->name
) && DBClassesAreEqual(*oldClass
, *newClass
)) {
489 CFStringAppendFormat(sql
, NULL
, CFSTR("DROP TABLE %@_old;"), (*oldClass
)->name
);
492 if(CFStringGetLength(sql
) > 0) {
493 require_action_quiet(ok
&= SecDbExec(dbt
, sql
, error
), out
,
494 LKAReportKeychainUpgradeOutcomeWithError(oldVersion
, newVersion
, LKAKeychainUpgradeOutcomePhase1DropOld
, error
? *error
: NULL
));
497 #if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
498 measureUpgradePhase1(&start
, ok
, SecBucket2Significant(itemsMigrated
));
502 query_destroy(query
, NULL
);
505 CFReleaseNull(classIndexesForNewTables
);
506 if(renamedOldClass
) {
507 CFReleaseNull(renamedOldClass
->name
);
508 free(renamedOldClass
);
513 __thread SecDbConnectionRef threadDbt
= NULL
;
515 // Goes through all tables represented by old_schema and tries to migrate all items from them into new (current version) tables.
516 static bool UpgradeItemPhase2(SecDbConnectionRef inDbt
, bool *inProgress
, int oldVersion
, CFErrorRef
*error
) {
517 SecDbConnectionRef oldDbt
= threadDbt
;
519 __block
bool ok
= true;
520 SecDbQueryRef query
= NULL
;
521 #if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
522 __block
int64_t itemsMigrated
= 0;
523 struct timeval start
;
525 gettimeofday(&start
, NULL
);
528 #if SECDB_BACKUPS_ENABLED
529 CFErrorRef backuperror
= NULL
;
530 if (!SecDbBackupCreateOrLoadBackupInfrastructure(&backuperror
)) {
531 secerror("upgr: failed to create backup infrastructure: %@", backuperror
);
533 secnotice("upgr", "setup backup infrastructure");
536 secnotice("upgr", "skipping backup setup for this platform");
539 // Go through all classes in new schema
540 const SecDbSchema
*newSchema
= current_schema();
541 int newVersion
= SCHEMA_VERSION(newSchema
);
542 for (const SecDbClass
*const *class = newSchema
->classes
; *class != NULL
&& !*inProgress
; class++) {
543 if(!((*class)->itemclass
)) {
544 //Don't try to decrypt non-item 'classes'
548 const SecDbAttr
*pdmn
= SecDbClassAttrWithKind(*class, kSecDbAccessAttr
, error
);
553 // Prepare query to go through all non-DK|DKU items
555 query_destroy(query
, NULL
);
557 require_action_quiet(query
= query_create(*class, SecMUSRGetAllViews(), NULL
, error
), out
, ok
= false);
558 ok
&= SecDbItemSelect(query
, threadDbt
, error
, NULL
, ^bool(const SecDbAttr
*attr
) {
559 // No simple per-attribute filtering.
561 }, ^bool(CFMutableStringRef sql
, bool *needWhere
) {
562 // Select only non-D-class items
563 SecDbAppendWhereOrAnd(sql
, needWhere
);
564 CFStringAppendFormat(sql
, NULL
, CFSTR("NOT %@ IN (?,?)"), pdmn
->name
);
566 }, ^bool(sqlite3_stmt
*stmt
, int col
) {
567 return SecDbBindObject(stmt
, col
++, kSecAttrAccessibleAlwaysPrivate
, error
) &&
568 SecDbBindObject(stmt
, col
++, kSecAttrAccessibleAlwaysThisDeviceOnlyPrivate
, error
);
569 }, ^(SecDbItemRef item
, bool *stop
) {
570 CFErrorRef localError
= NULL
;
572 #if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
577 if (SecDbItemEnsureDecrypted(item
, true, &localError
)) {
579 // Delete SHA1 field from the item, so that it is newly recalculated before storing
580 // the item into the new table.
581 require_quiet(ok
= SecDbItemSetValue(item
, SecDbClassAttrWithKind(item
->class, kSecDbSHA1Attr
, error
),
582 kCFNull
, &localError
), out
);
583 // Drop items with kSecAttrAccessGroupToken, as these items should not be there at all. Since agrp attribute
584 // is always stored as cleartext in the DB column, we can always rely on this attribute being present in item->attributes.
585 // <rdar://problem/33401870>
586 if (CFEqualSafe(SecDbItemGetCachedValueWithName(item
, kSecAttrAccessGroup
), kSecAttrAccessGroupToken
) &&
587 SecDbItemGetCachedValueWithName(item
, kSecAttrTokenID
) == NULL
) {
588 secnotice("upgr", "dropping item during item upgrade due to agrp=com.apple.token: %@", item
);
589 ok
= SecDbItemDelete(item
, threadDbt
, kCFBooleanFalse
, &localError
);
591 // Replace item with the new value in the table; this will cause the item to be decoded and recoded back,
592 // incl. recalculation of item's hash.
593 ok
= SecDbItemUpdate(item
, item
, threadDbt
, false, query
->q_uuid_from_primary_key
, &localError
);
598 CFIndex status
= CFErrorGetCode(localError
);
602 // Items producing errSecDecode are silently dropped - they are not decodable and lost forever.
603 // make sure we use a local error so that this error is not proppaged upward and cause a
604 // migration failure.
605 CFErrorRef deleteError
= NULL
;
606 (void)SecDbItemDelete(item
, threadDbt
, false, &deleteError
);
607 CFReleaseNull(deleteError
);
611 case errSecInteractionNotAllowed
:
612 // If we are still not able to decrypt the item because the class key is not released yet,
613 // remember that DB still needs phase2 migration to be run next time a connection is made. Also
614 // stop iterating next items, it would be just waste of time because the whole iteration will be run
615 // next time when this phase2 will be rerun.
616 LKAReportKeychainUpgradeOutcome(oldVersion
, newVersion
, LKAKeychainUpgradeOutcomeLocked
);
621 case errSecAuthNeeded
:
622 // errSecAuthNeeded means that it is an ACL-based item which requires authentication (or at least
623 // ACM context, which we do not have).
626 case SQLITE_CONSTRAINT
: // yeah...
627 if (!CFEqual(kSecDbErrorDomain
, CFErrorGetDomain(localError
))) {
628 secerror("Received SQLITE_CONSTRAINT with wrong error domain. Huh? Item: %@, error: %@", item
, localError
);
631 case errSecDuplicateItem
:
632 // continue to upgrade and don't propagate errors for insert failures
633 // that are typical of a single item failure
634 secnotice("upgr", "Ignoring duplicate item: %@", item
);
635 secdebug("upgr", "Duplicate item error: %@", localError
);
639 case kAKSReturnNotReady
:
640 case kAKSReturnTimeout
:
642 case errSecNotAvailable
:
643 *inProgress
= true; // We're not done, call me again later!
644 secnotice("upgr", "Bailing in phase 2 because AKS is unavailable: %@", localError
);
647 // Other errors should abort the migration completely.
648 ok
= CFErrorPropagate(CFRetainSafe(localError
), error
);
654 CFReleaseSafe(localError
);
655 *stop
= *stop
|| !ok
;
658 require_action(ok
, out
, LKAReportKeychainUpgradeOutcomeWithError(oldVersion
, newVersion
, LKAKeychainUpgradeOutcomePhase2
, error
? *error
: NULL
));
661 #if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
662 measureUpgradePhase2(&start
, SecBucket2Significant(itemsMigrated
));
667 query_destroy(query
, NULL
);
673 static bool SecKeychainDbUpgradeFromVersion(SecDbConnectionRef dbt
, int version
, bool *inProgress
, CFErrorRef
*error
) {
674 __block
bool didPhase2
= false;
675 __block
bool ok
= true;
676 __block CFErrorRef localError
= NULL
;
681 const SecDbSchema
*newSchema
= current_schema();
682 int newVersion
= SCHEMA_VERSION(newSchema
);
683 bool skipped_upgrade
= false;
685 // If DB schema is the one we want, we are done.
686 require_action_quiet(SCHEMA_VERSION(newSchema
) != version
, out
, skipped_upgrade
= true);
688 // Check if the schema of the database on disk is the same major, but newer version then what we have
689 // in code, lets just skip this since a newer version of the OS have upgrade it. Since its the same
690 // major, its a promise that it will be compatible.
691 if (newSchema
->majorVersion
== VERSION_MAJOR(version
) && newSchema
->minorVersion
< VERSION_MINOR(version
)) {
692 secnotice("upgr", "skipping upgrade since minor is newer");
696 ok
&= SecDbTransaction(dbt
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
697 CFStringRef sql
= NULL
;
698 bool didPhase1
= false;
700 // Get version again once we start a transaction, someone else might change the migration state.
702 require_quiet(ok
= SecKeychainDbGetVersion(dbt
, &version2
, &localError
), out
);
703 // Check if someone has raced us to the migration of the database
704 require_action(version
== version2
, out
, CFReleaseNull(localError
); ok
= true);
706 require_quiet(SCHEMA_VERSION(newSchema
) != version2
, out
);
708 // If this is empty database, just create table according to schema and be done with it.
709 require_action_quiet(version2
!= 0, out
, ok
= SecItemDbCreateSchema(dbt
, newSchema
, NULL
, true, &localError
);
710 LKAReportKeychainUpgradeOutcomeWithError(version2
, newVersion
, LKAKeychainUpgradeOutcomeNewDb
, localError
));
712 int oldVersion
= VERSION_OLD(version2
);
713 version2
= VERSION_NEW(version2
);
715 require_action_quiet(version2
== SCHEMA_VERSION(newSchema
) || oldVersion
== 0, out
,
716 ok
= SecDbError(SQLITE_CORRUPT
, &localError
,
717 CFSTR("Half migrated but obsolete DB found: found 0x%x(0x%x) but 0x%x is needed"),
718 version2
, oldVersion
, SCHEMA_VERSION(newSchema
));
719 LKAReportKeychainUpgradeOutcome(version2
, newVersion
, LKAKeychainUpgradeOutcomeObsoleteDb
));
721 // Check whether we have both old and new tables in the DB.
722 if (oldVersion
== 0) {
723 // Pure old-schema migration attempt, with full blown table renames etc (a.k.a. phase1)
724 oldVersion
= version2
;
725 version2
= SCHEMA_VERSION(newSchema
);
727 // Find schema for old database.
728 const SecDbSchema
*oldSchema
= NULL
;
729 for (const SecDbSchema
* const *pschema
= all_schemas(); *pschema
; ++pschema
) {
730 if (SCHEMA_VERSION((*pschema
)) == oldVersion
) {
731 oldSchema
= *pschema
;
736 // If we are attempting to upgrade from a version for which we have no schema, fail.
737 require_action_quiet(oldSchema
!= NULL
, out
,
738 ok
= SecDbError(SQLITE_CORRUPT
, &localError
, CFSTR("no schema for version: 0x%x"), oldVersion
);
739 secerror("no schema for version 0x%x", oldVersion
);
740 LKAReportKeychainUpgradeOutcome(version2
, newVersion
, LKAKeychainUpgradeOutcomeNoSchema
));
742 secnotice("upgr", "Upgrading from version 0x%x to 0x%x", oldVersion
, SCHEMA_VERSION(newSchema
));
743 SecSignpostStart(SecSignpostUpgradePhase1
);
744 require_action(ok
= UpgradeSchemaPhase1(dbt
, oldSchema
, &localError
), out
, secerror("upgrade: Upgrade phase1 failed: %@", localError
));
745 SecSignpostStop(SecSignpostUpgradePhase1
);
751 CFErrorRef phase2Error
= NULL
;
753 SecSignpostStart(SecSignpostUpgradePhase2
);
755 // Lets try to go through non-D-class items in new tables and apply decode/encode on them
756 // If this fails the error will be ignored after doing a phase1 since but not in the second
757 // time when we are doing phase2.
758 ok
= UpgradeItemPhase2(dbt
, inProgress
, version2
, &phase2Error
);
763 CFReleaseNull(phase2Error
);
765 SecErrorPropagate(phase2Error
, &localError
);
768 require_action(ok
, out
, secerror("upgrade: Upgrade phase2 (%d) failed: %@", didPhase1
, localError
));
771 // If either migration path we did reported that the migration was complete, signalize that
772 // in the version database by cleaning oldVersion (which is stored in upper halfword of the version)
773 secnotice("upgr", "Done upgrading from version 0x%x to 0x%x", oldVersion
, SCHEMA_VERSION(newSchema
));
777 SecSignpostStop(SecSignpostUpgradePhase2
);
781 // Update database version table.
782 uint32_t major
= (VERSION_MAJOR(version2
)) | (VERSION_MAJOR(oldVersion
) << 16);
783 uint32_t minor
= (VERSION_MINOR(version2
)) | (VERSION_MINOR(oldVersion
) << 16);
784 secnotice("upgr", "Upgrading saving version major 0x%x minor 0x%x", major
, minor
);
785 sql
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("UPDATE tversion SET version='%d', minor='%d'"),
787 require_action_quiet(ok
= SecDbExec(dbt
, sql
, &localError
), out
, secerror("upgrade: Setting version failed: %@", localError
));
791 secerror("upgrade: SecDB upgrade failed: %@", localError
);
797 if (ok
&& didPhase2
) {
798 #if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
799 SecADSetValueForScalarKey(CFSTR("com.apple.keychain.migration-success"), 1);
804 if (!ok
|| localError
) {
805 // TODO: This logic should be inverted to a do-not-corrupt-unless default, <rdar://problem/29771874>
807 * We assume that database is corrupt at this point, but we need to
808 * check if the error we got isn't severe enough to mark the database as corrupt.
809 * In those cases we opt out of corrupting the database.
811 bool markedCorrupt
= true;
814 secwarning("upgrade: error has been set but status is true");
817 secerror("upgrade: error occurred, considering marking database as corrupt: %@", localError
);
819 CFStringRef domain
= CFErrorGetDomain(localError
);
820 CFIndex code
= CFErrorGetCode(localError
);
822 if ((CFEqualSafe(domain
, kSecDbErrorDomain
) &&
823 ((code
& 0xff) == SQLITE_LOCKED
|| (code
& 0xff) == SQLITE_BUSY
|| (code
& 0xff) == SQLITE_FULL
)) ||
825 code
== kAKSReturnNotReady
|| code
== kAKSReturnTimeout
||
827 code
== errSecNotAvailable
)
829 secerror("upgrade: not marking keychain database corrupt for error: %@", localError
);
830 markedCorrupt
= false;
831 CFReleaseNull(localError
);
833 secerror("upgrade: unable to complete upgrade, marking DB as corrupt: %@", localError
);
836 secerror("upgrade: unable to complete upgrade and no error object returned, marking DB as corrupt");
839 secerror("upgrade: marking database as corrupt");
840 SecDbCorrupt(dbt
, localError
);
841 #if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
842 SecADSetValueForScalarKey(CFSTR("com.apple.keychain.migration-failure"), 1);
846 // Things seemed to go okay!
848 LKAReportKeychainUpgradeOutcome(version
, newVersion
, LKAKeychainUpgradeOutcomeSuccess
);
851 //If we're done here, we should opportunistically re-add all indices (just in case)
852 if(skipped_upgrade
|| didPhase2
) {
853 // Create indices, ignoring all errors
854 for (SecDbClass
const* const* newClass
= newSchema
->classes
; *newClass
; ++newClass
) {
855 SecDbForEachAttrWithMask((*newClass
), desc
, kSecDbIndexFlag
| kSecDbInFlag
) {
856 CFStringRef sql
= NULL
;
857 CFErrorRef classLocalError
= NULL
;
860 if (desc
->kind
== kSecDbSyncAttr
) {
861 // Replace the complete sync index with a partial index for sync=0. Most items are sync=1, so the complete index isn't helpful for sync=1 queries.
862 sql
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("DROP INDEX IF EXISTS %@%@; CREATE INDEX IF NOT EXISTS %@%@0 on %@(%@) WHERE %@=0;"),
863 (*newClass
)->name
, desc
->name
, (*newClass
)->name
, desc
->name
, (*newClass
)->name
, desc
->name
, desc
->name
);
865 sql
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("CREATE INDEX IF NOT EXISTS %@%@ ON %@(%@);"), (*newClass
)->name
, desc
->name
, (*newClass
)->name
, desc
->name
);
867 localOk
&= SecDbExec(dbt
, sql
, &classLocalError
);
871 secerror("upgrade: unable to opportunistically create index (%@,%@): %@", (*newClass
)->name
, desc
->name
, classLocalError
);
873 CFReleaseNull(classLocalError
);
880 *error
= (CFErrorRef
)CFRetain(localError
);
882 CFReleaseNull(localError
);
888 static bool accessGroupIsNetworkExtensionAndClientIsEntitled(CFStringRef accessGroup
, SecurityClient
* client
)
890 return client
&& client
->canAccessNetworkExtensionAccessGroups
&& accessGroup
&& CFStringHasSuffix(accessGroup
, kSecNetworkExtensionAccessGroupSuffix
);
893 /* AUDIT[securityd](done):
894 accessGroup (ok) is a caller provided, non NULL CFTypeRef.
896 Return true iff accessGroup is allowable according to accessGroups.
898 bool accessGroupsAllows(CFArrayRef accessGroups
, CFStringRef accessGroup
, SecurityClient
* client
) {
899 /* NULL accessGroups is wildcard. */
902 /* Make sure we have a string. */
903 if (!isString(accessGroup
))
906 /* Having the special accessGroup "*" allows access to all accessGroups. */
907 CFRange range
= { 0, CFArrayGetCount(accessGroups
) };
909 (CFArrayContainsValue(accessGroups
, range
, accessGroup
) ||
910 CFArrayContainsValue(accessGroups
, range
, CFSTR("*")) ||
911 accessGroupIsNetworkExtensionAndClientIsEntitled(accessGroup
, client
)))
917 bool itemInAccessGroup(CFDictionaryRef item
, CFArrayRef accessGroups
) {
918 return accessGroupsAllows(accessGroups
,
919 CFDictionaryGetValue(item
, kSecAttrAccessGroup
), NULL
);
923 static CF_RETURNS_RETAINED CFDataRef
SecServerExportBackupableKeychain(SecDbConnectionRef dbt
,
924 SecurityClient
*client
,
925 keybag_handle_t src_keybag
, keybag_handle_t dest_keybag
, CFErrorRef
*error
) {
926 CFDataRef data_out
= NULL
;
928 SecSignpostStart(SecSignpostBackupKeychainBackupable
);
930 /* Export everything except the items for which SecItemIsSystemBound()
932 CFDictionaryRef keychain
= SecServerCopyKeychainPlist(dbt
, client
,
933 src_keybag
, dest_keybag
, kSecBackupableItemFilter
,
936 data_out
= CFPropertyListCreateData(kCFAllocatorDefault
, keychain
,
937 kCFPropertyListBinaryFormat_v1_0
,
941 SecSignpostStop(SecSignpostBackupKeychainBackupable
);
946 static bool SecServerImportBackupableKeychain(SecDbConnectionRef dbt
,
947 SecurityClient
*client
,
948 keybag_handle_t src_keybag
,
949 keybag_handle_t dest_keybag
,
953 return kc_transaction(dbt
, error
, ^{
955 CFDictionaryRef keychain
;
957 SecSignpostStart(SecSignpostRestoreKeychainBackupable
);
959 keychain
= CFPropertyListCreateWithData(kCFAllocatorDefault
, data
,
960 kCFPropertyListImmutable
, NULL
,
963 if (isDictionary(keychain
)) {
964 ok
= SecServerImportKeychainInPlist(dbt
,
969 kSecBackupableItemFilter
,
970 false, // Restoring backup should not remove stuff that got into the keychain before us
973 ok
= SecError(errSecParam
, error
, CFSTR("import: keychain is not a dictionary"));
978 SecSignpostStop(SecSignpostRestoreKeychainBackupable
);
986 * Similar to ks_open_keybag, but goes through MKB interface
988 static bool mkb_open_keybag(CFDataRef keybag
, CFDataRef password
, MKBKeyBagHandleRef
*handle
, bool emcs
, CFErrorRef
*error
) {
990 MKBKeyBagHandleRef mkbhandle
= NULL
;
992 rc
= MKBKeyBagCreateWithData(keybag
, &mkbhandle
);
993 if (rc
!= kMobileKeyBagSuccess
) {
994 return SecKernError(rc
, error
, CFSTR("MKBKeyBagCreateWithData failed: %d"), rc
);
998 rc
= MKBKeyBagUnlock(mkbhandle
, password
);
999 if (rc
!= kMobileKeyBagSuccess
) {
1000 CFRelease(mkbhandle
);
1001 return SecKernError(rc
, error
, CFSTR("failed to unlock bag: %d"), rc
);
1004 secnotice("keychainbackup", "skipping keybag unlock for EMCS");
1007 *handle
= mkbhandle
;
1014 static CFDataRef
SecServerKeychainCreateBackup(SecDbConnectionRef dbt
, SecurityClient
*client
, CFDataRef keybag
,
1015 CFDataRef password
, bool emcs
, CFErrorRef
*error
) {
1016 CFDataRef backup
= NULL
;
1017 keybag_handle_t backup_keybag
;
1019 SecSignpostStart(SecSignpostBackupOpenKeybag
);
1022 MKBKeyBagHandleRef mkbhandle
= NULL
;
1023 require(mkb_open_keybag(keybag
, password
, &mkbhandle
, emcs
, error
), out
);
1025 require_noerr(MKBKeyBagGetAKSHandle(mkbhandle
, &backup_keybag
), out
);
1028 backup_keybag
= KEYBAG_NONE
;
1030 SecSignpostStop(SecSignpostBackupOpenKeybag
);
1031 SecSignpostStart(SecSignpostBackupKeychain
);
1033 /* Export from system keybag to backup keybag. */
1034 backup
= SecServerExportBackupableKeychain(dbt
, client
, KEYBAG_DEVICE
, backup_keybag
, error
);
1038 SecSignpostStop(SecSignpostBackupOpenKeybag
);
1041 CFRelease(mkbhandle
);
1046 static bool SecServerKeychainRestore(SecDbConnectionRef dbt
,
1047 SecurityClient
*client
,
1054 keybag_handle_t backup_keybag
;
1056 secnotice("SecServerKeychainRestore", "Restoring keychain backup");
1058 SecSignpostStart(SecSignpostRestoreOpenKeybag
);
1060 MKBKeyBagHandleRef mkbhandle
= NULL
;
1061 require(mkb_open_keybag(keybag
, password
, &mkbhandle
, false, error
), out
);
1063 require_noerr(MKBKeyBagGetAKSHandle(mkbhandle
, &backup_keybag
), out
);
1065 backup_keybag
= KEYBAG_NONE
;
1067 SecSignpostStop(SecSignpostRestoreOpenKeybag
);
1068 SecSignpostStart(SecSignpostRestoreKeychain
);
1070 /* Import from backup keybag to system keybag. */
1071 require(SecServerImportBackupableKeychain(dbt
, client
, backup_keybag
, KEYBAG_DEVICE
, backup
, error
), out
);
1075 SecSignpostStop(SecSignpostRestoreKeychain
);
1078 CFRelease(mkbhandle
);
1082 secnotice("SecServerKeychainRestore", "Restore completed successfully");
1084 secwarning("SecServerKeychainRestore: Restore failed with: %@", error
? *error
: NULL
);
1090 // MARK - External SPI support code.
1092 CFStringRef
__SecKeychainCopyPath(void) {
1093 CFStringRef kcRelPath
= NULL
;
1095 if (os_variant_is_recovery("securityd")) {
1096 kcRelPath
= CFSTR("keychain-recovery-2.db");
1097 } else if (use_hwaes()) {
1098 kcRelPath
= CFSTR("keychain-2.db");
1100 kcRelPath
= CFSTR("keychain-2-debug.db");
1103 CFStringRef kcPath
= NULL
;
1104 CFURLRef kcURL
= SecCopyURLForFileInKeychainDirectory(kcRelPath
);
1106 kcPath
= CFURLCopyFileSystemPath(kcURL
, kCFURLPOSIXPathStyle
);
1113 // MARK: kc_dbhandle init and reset
1115 SecDbRef
SecKeychainDbCreate(CFStringRef path
, CFErrorRef
* error
) {
1116 __block CFErrorRef localerror
= NULL
;
1118 SecDbRef kc
= SecDbCreate(path
, 0600, true, true, true, true, kSecDbMaxIdleHandles
,
1119 ^bool (SecDbRef db
, SecDbConnectionRef dbconn
, bool didCreate
, bool *callMeAgainForNextConnection
, CFErrorRef
*createError
)
1121 // Upgrade from version 0 means create the schema in empty db.
1125 ok
= SecKeychainDbGetVersion(dbconn
, &version
, createError
);
1127 ok
= ok
&& SecKeychainDbUpgradeFromVersion(dbconn
, version
, callMeAgainForNextConnection
, createError
);
1129 secerror("Upgrade %sfailed: %@", didCreate
? "from v0 " : "", createError
? *createError
: NULL
);
1131 localerror
= createError
? *createError
: NULL
;
1134 // This block might get called many, many times due to callMeAgainForNextConnection.
1135 // When we no longer want to be called, we believe we're done. Begin the rest of initialization.
1136 if( !callMeAgainForNextConnection
|| !(*callMeAgainForNextConnection
)) {
1137 SecKeychainDbInitialize(db
);
1145 SecDbSetCorruptionReset(kc
, ^{
1146 SecDbResetMetadataKeys();
1151 *error
= localerror
;
1157 SecDbRef
SecKeychainDbInitialize(SecDbRef db
) {
1160 if(SecCKKSIsEnabled()) {
1161 // This needs to be async, otherwise we get hangs between securityd, cloudd, and apsd
1162 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT
, 0), ^{
1163 SecCKKSInitialize(db
);
1168 if(OctagonIsEnabled() && OctagonShouldPerformInitialization()) {
1169 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT
, 0), ^{
1170 OctagonInitialize();
1174 if(EscrowRequestServerIsEnabled()) {
1175 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT
, 0), ^{
1176 EscrowRequestServerInitialize();
1184 static SecDbRef _kc_dbhandle
= NULL
;
1185 static dispatch_queue_t _kc_dbhandle_dispatch
= NULL
;
1186 static dispatch_once_t _kc_dbhandle_dispatch_onceToken
= 0;
1187 static dispatch_queue_t
get_kc_dbhandle_dispatch() {
1188 dispatch_once(&_kc_dbhandle_dispatch_onceToken
, ^{
1189 _kc_dbhandle_dispatch
= dispatch_queue_create("sec_kc_dbhandle", DISPATCH_QUEUE_SERIAL
);
1192 return _kc_dbhandle_dispatch
;
1195 static bool kc_dbhandle_init(CFErrorRef
* error
) {
1196 SecDbRef oldHandle
= _kc_dbhandle
;
1197 _kc_dbhandle
= NULL
;
1198 CFStringRef dbPath
= __SecKeychainCopyPath();
1200 _kc_dbhandle
= SecKeychainDbCreate(dbPath
, error
);
1203 secerror("no keychain path available");
1206 secerror("replaced %@ with %@", oldHandle
, _kc_dbhandle
);
1207 CFRelease(oldHandle
);
1209 // Having a dbhandle means we succeeded.
1210 return !!_kc_dbhandle
;
1213 static SecDbRef
kc_dbhandle(CFErrorRef
* error
)
1215 dispatch_sync(get_kc_dbhandle_dispatch(), ^{
1216 if(_kc_dbhandle
== NULL
) {
1217 _SecDbServerSetup();
1218 kc_dbhandle_init(error
);
1221 return _kc_dbhandle
;
1224 /* For whitebox testing only */
1225 void SecKeychainDbReset(dispatch_block_t inbetween
)
1227 dispatch_sync(get_kc_dbhandle_dispatch(), ^{
1228 CFReleaseNull(_kc_dbhandle
);
1229 SecDbResetMetadataKeys();
1236 static bool kc_acquire_dbt(bool writeAndRead
, SecDbConnectionRef
* dbconn
, CFErrorRef
*error
) {
1237 SecDbRef db
= kc_dbhandle(error
);
1239 if(error
&& !(*error
)) {
1240 SecError(errSecDataNotAvailable
, error
, CFSTR("failed to get a db handle"));
1245 return SecDbConnectionAcquireRefMigrationSafe(db
, !writeAndRead
, dbconn
, error
);
1248 /* Return a per thread dbt handle for the keychain. If create is true create
1249 the database if it does not yet exist. If it is false, just return an
1250 error if it fails to auto-create. */
1251 bool kc_with_dbt(bool writeAndRead
, CFErrorRef
*error
, bool (^perform
)(SecDbConnectionRef dbt
))
1253 return kc_with_custom_db(writeAndRead
, true, NULL
, error
, perform
);
1256 bool kc_with_dbt_non_item_tables(bool writeAndRead
, CFErrorRef
* error
, bool (^perform
)(SecDbConnectionRef dbt
))
1258 return kc_with_custom_db(writeAndRead
, false, NULL
, error
, perform
);
1261 bool kc_with_custom_db(bool writeAndRead
, bool usesItemTables
, SecDbRef db
, CFErrorRef
*error
, bool (^perform
)(SecDbConnectionRef dbt
))
1263 if (db
&& db
!= kc_dbhandle(error
)) {
1264 __block
bool result
= false;
1266 SecDbPerformWrite(db
, error
, ^(SecDbConnectionRef dbconn
) {
1267 result
= perform(dbconn
);
1271 SecDbPerformRead(db
, error
, ^(SecDbConnectionRef dbconn
) {
1272 result
= perform(dbconn
);
1279 // The kc_with_dbt upthread will clean this up when it's done.
1280 return perform(threadDbt
);
1283 if (writeAndRead
&& usesItemTables
) {
1284 #if SECUREOBJECTSYNC
1285 SecItemDataSourceFactoryGetDefault();
1290 if (kc_acquire_dbt(writeAndRead
, &threadDbt
, error
)) {
1291 ok
= perform(threadDbt
);
1292 SecDbConnectionRelease(threadDbt
);
1299 items_matching_issuer_parent(SecDbConnectionRef dbt
, CFArrayRef accessGroups
, CFDataRef musrView
,
1300 CFDataRef issuer
, CFArrayRef issuers
, int recurse
)
1303 CFArrayRef results
= NULL
;
1307 if (CFArrayContainsValue(issuers
, CFRangeMake(0, CFArrayGetCount(issuers
)), issuer
))
1310 /* XXX make musr supported */
1311 const void *keys
[] = { kSecClass
, kSecReturnRef
, kSecAttrSubject
};
1312 const void *vals
[] = { kSecClassCertificate
, kCFBooleanTrue
, issuer
};
1313 CFDictionaryRef query
= CFDictionaryCreate(kCFAllocatorDefault
, keys
, vals
, array_size(keys
), NULL
, NULL
);
1318 CFErrorRef localError
= NULL
;
1319 q
= query_create_with_limit(query
, musrView
, kSecMatchUnlimited
, &localError
);
1322 s3dl_copy_matching(dbt
, q
, (CFTypeRef
*)&results
, accessGroups
, &localError
);
1323 query_destroy(q
, &localError
);
1326 secerror("items matching issuer parent: %@", localError
);
1327 CFReleaseNull(localError
);
1331 count
= CFArrayGetCount(results
);
1332 for (i
= 0; (i
< count
) && !found
; i
++) {
1333 CFDictionaryRef cert_dict
= (CFDictionaryRef
)CFArrayGetValueAtIndex(results
, i
);
1334 CFDataRef cert_issuer
= CFDictionaryGetValue(cert_dict
, kSecAttrIssuer
);
1335 if (CFEqual(cert_issuer
, issuer
))
1338 found
= items_matching_issuer_parent(dbt
, accessGroups
, musrView
, cert_issuer
, issuers
, recurse
);
1340 CFReleaseSafe(results
);
1346 _FilterWithPolicy(SecPolicyRef policy
, CFDateRef date
, SecCertificateRef cert
)
1348 CFDictionaryRef props
= NULL
;
1349 CFArrayRef keychains
= NULL
;
1350 CFArrayRef anchors
= NULL
;
1351 CFArrayRef certs
= NULL
;
1352 CFArrayRef chain
= NULL
;
1353 SecTrustRef trust
= NULL
;
1355 SecTrustResultType trustResult
;
1356 Boolean needChain
= false;
1357 __block
bool ok
= false;
1359 if (!policy
|| !cert
) return false;
1361 certs
= CFArrayCreate(NULL
, (const void **)&cert
, (CFIndex
)1, &kCFTypeArrayCallBacks
);
1362 require_noerr_quiet(SecTrustCreateWithCertificates(certs
, policy
, &trust
), cleanup
);
1364 /* Set evaluation date, if specified (otherwise current date is implied) */
1365 if (date
&& (CFGetTypeID(date
) == CFDateGetTypeID())) {
1366 require_noerr_quiet(SecTrustSetVerifyDate(trust
, date
), cleanup
);
1369 /* Check whether this is the X509 Basic policy, which means chain building */
1370 props
= SecPolicyCopyProperties(policy
);
1372 CFTypeRef oid
= (CFTypeRef
) CFDictionaryGetValue(props
, kSecPolicyOid
);
1373 if (oid
&& (CFEqual(oid
, kSecPolicyAppleX509Basic
) ||
1374 CFEqual(oid
, kSecPolicyAppleRevocation
))) {
1380 require_noerr_quiet(SecTrustEvaluateLeafOnly(trust
, &trustResult
), cleanup
);
1382 require_noerr_quiet(SecTrustEvaluate(trust
, &trustResult
), cleanup
);
1385 require_quiet((trustResult
== kSecTrustResultProceed
||
1386 trustResult
== kSecTrustResultUnspecified
||
1387 trustResult
== kSecTrustResultRecoverableTrustFailure
), cleanup
);
1390 #if TARGET_OS_IPHONE
1391 CFArrayRef properties
= SecTrustCopyProperties(trust
);
1393 CFArrayRef properties
= SecTrustCopyProperties_ios(trust
);
1396 CFArrayForEach(properties
, ^(const void *property
) {
1397 CFDictionaryForEach((CFDictionaryRef
)property
, ^(const void *key
, const void *value
) {
1398 if (CFEqual((CFTypeRef
)key
, kSecPropertyKeyType
) && CFEqual((CFTypeRef
)value
, kSecPropertyTypeError
))
1402 CFRelease(properties
);
1406 if(props
) CFRelease(props
);
1407 if(chain
) CFRelease(chain
);
1408 if(anchors
) CFRelease(anchors
);
1409 if(keychains
) CFRelease(keychains
);
1410 if(certs
) CFRelease(certs
);
1411 if(trust
) CFRelease(trust
);
1417 _FilterWithDate(CFDateRef validOnDate
, SecCertificateRef cert
)
1419 if (!validOnDate
|| !cert
) return false;
1421 CFAbsoluteTime at
, nb
, na
;
1422 at
= CFDateGetAbsoluteTime((CFDateRef
)validOnDate
);
1425 nb
= SecCertificateNotValidBefore(cert
);
1426 na
= SecCertificateNotValidAfter(cert
);
1428 if (nb
== 0 || na
== 0 || nb
== na
) {
1430 secnotice("FilterWithDate", "certificate cannot operate");
1434 secnotice("FilterWithDate", "certificate is not valid yet");
1438 secnotice("FilterWithDate", "certificate expired");
1445 _FilterWithTrust(Boolean trustedOnly
, SecCertificateRef cert
)
1447 if (!cert
) return false;
1448 if (!trustedOnly
) return true;
1451 CFArrayRef certArray
= CFArrayCreate(NULL
, (const void**)&cert
, 1, &kCFTypeArrayCallBacks
);
1452 SecTrustRef trust
= NULL
;
1453 SecPolicyRef policy
= SecPolicyCreateBasicX509();
1454 require_quiet(policy
, out
);
1456 require_noerr_quiet(SecTrustCreateWithCertificates(certArray
, policy
, &trust
), out
);
1457 SecTrustResultType trustResult
;
1458 require_noerr_quiet(SecTrustEvaluate(trust
, &trustResult
), out
);
1460 require_quiet((trustResult
== kSecTrustResultProceed
||
1461 trustResult
== kSecTrustResultUnspecified
), out
);
1464 CFReleaseSafe(trust
);
1465 CFReleaseSafe(policy
);
1466 CFReleaseSafe(certArray
);
1470 static SecCertificateRef
1471 CopyCertificateFromItem(Query
*q
, CFDictionaryRef item
) {
1472 SecCertificateRef certRef
= NULL
;
1473 CFDictionaryRef itemValue
= NULL
;
1475 CFTypeRef tokenID
= NULL
;
1476 CFDataRef certData
= NULL
;
1477 if (q
->q_class
== identity_class()) {
1478 certData
= CFDictionaryGetValue(item
, kSecAttrIdentityCertificateData
);
1479 tokenID
= CFDictionaryGetValue(item
, kSecAttrIdentityCertificateTokenID
);
1480 } else if (q
->q_class
== cert_class()) {
1481 certData
= CFDictionaryGetValue(item
, kSecValueData
);
1482 tokenID
= CFDictionaryGetValue(item
, kSecAttrTokenID
);
1485 require_quiet(certData
, out
);
1486 if (tokenID
!= NULL
) {
1487 CFErrorRef error
= NULL
;
1488 itemValue
= SecTokenItemValueCopy(certData
, &error
);
1489 require_action_quiet(itemValue
, out
, { secerror("function SecTokenItemValueCopy failed with: %@", error
); CFReleaseSafe(error
); });
1490 CFDataRef tokenCertData
= CFDictionaryGetValue(itemValue
, kSecTokenValueDataKey
);
1491 require_action_quiet(tokenCertData
, out
, { secerror("token item doesn't contain token value data");});
1492 certRef
= SecCertificateCreateWithData(kCFAllocatorDefault
, tokenCertData
);
1495 certRef
= SecCertificateCreateWithData(kCFAllocatorDefault
, certData
);
1498 CFReleaseNull(itemValue
);
1502 bool match_item(SecDbConnectionRef dbt
, Query
*q
, CFArrayRef accessGroups
, CFDictionaryRef item
)
1505 SecCertificateRef certRef
= NULL
;
1506 if (q
->q_match_issuer
) {
1507 CFDataRef issuer
= CFDictionaryGetValue(item
, kSecAttrIssuer
);
1508 if (!items_matching_issuer_parent(dbt
, accessGroups
, q
->q_musrView
, issuer
, q
->q_match_issuer
, 10 /*max depth*/))
1512 if (q
->q_match_policy
&& (q
->q_class
== identity_class() || q
->q_class
== cert_class())) {
1514 certRef
= CopyCertificateFromItem(q
, item
);
1515 require_quiet(certRef
, out
);
1516 require_quiet(_FilterWithPolicy(q
->q_match_policy
, q
->q_match_valid_on_date
, certRef
), out
);
1519 if (q
->q_match_valid_on_date
&& (q
->q_class
== identity_class() || q
->q_class
== cert_class())) {
1521 certRef
= CopyCertificateFromItem(q
, item
);
1522 require_quiet(certRef
, out
);
1523 require_quiet(_FilterWithDate(q
->q_match_valid_on_date
, certRef
), out
);
1526 if (q
->q_match_trusted_only
&& (q
->q_class
== identity_class() || q
->q_class
== cert_class())) {
1528 certRef
= CopyCertificateFromItem(q
, item
);
1529 require_quiet(certRef
, out
);
1530 require_quiet(_FilterWithTrust(CFBooleanGetValue(q
->q_match_trusted_only
), certRef
), out
);
1533 /* Add future match checks here. */
1536 CFReleaseSafe(certRef
);
1540 /****************************************************************************
1541 **************** Beginning of Externally Callable Interface ****************
1542 ****************************************************************************/
1544 static bool SecEntitlementError(CFErrorRef
*error
)
1547 #define SEC_ENTITLEMENT_WARNING CFSTR("com.apple.application-identifier nor com.apple.security.application-groups nor keychain-access-groups")
1548 #elif TARGET_OS_IOSMAC
1549 #define SEC_ENTITLEMENT_WARNING CFSTR("com.apple.developer.associated-application-identifier nor application-identifier nor com.apple.security.application-groups nor keychain-access-groups")
1551 #define SEC_ENTITLEMENT_WARNING CFSTR("application-identifier nor keychain-access-groups")
1554 return SecError(errSecMissingEntitlement
, error
, CFSTR("Client has neither %@ entitlements"), SEC_ENTITLEMENT_WARNING
);
1557 static bool SecEntitlementErrorForExplicitAccessGroup(CFStringRef agrp
, CFArrayRef clientGroups
, CFErrorRef
* error
)
1559 return SecError(errSecMissingEntitlement
, error
, CFSTR("Client explicitly specifies access group %@ but is only entitled for %@"), agrp
, clientGroups
);
1562 static CFStringRef
CopyAccessGroupForRowID(sqlite_int64 rowID
, CFStringRef itemClass
)
1564 __block CFStringRef accessGroup
= NULL
;
1566 __block CFErrorRef error
= NULL
;
1567 bool ok
= kc_with_dbt(false, &error
, ^bool(SecDbConnectionRef dbt
) {
1568 CFStringRef table
= CFEqual(itemClass
, kSecClassIdentity
) ? kSecClassCertificate
: itemClass
;
1569 CFStringRef sql
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("SELECT agrp FROM %@ WHERE rowid == %u"), table
, (unsigned int)rowID
);
1570 bool dbOk
= SecDbWithSQL(dbt
, sql
, &error
, ^bool(sqlite3_stmt
*stmt
) {
1571 bool rowOk
= SecDbForEach(dbt
, stmt
, &error
, ^bool(int row_index
) {
1572 accessGroup
= CFStringCreateWithBytes(NULL
, sqlite3_column_blob(stmt
, 0), sqlite3_column_bytes(stmt
, 0), kCFStringEncodingUTF8
, false);
1573 return accessGroup
!= NULL
;
1576 return (bool)(rowOk
&& accessGroup
!= NULL
);
1580 return (bool)(dbOk
&& accessGroup
);
1587 CFReleaseNull(accessGroup
);
1592 /* AUDIT[securityd](done):
1593 query (ok) is a caller provided dictionary, only its cf type has been checked.
1596 SecItemServerCopyMatching(CFDictionaryRef query
, CFTypeRef
*result
,
1597 SecurityClient
*client
, CFErrorRef
*error
)
1599 CFArrayRef accessGroups
= CFRetainSafe(client
->accessGroups
);
1602 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
))) {
1603 CFReleaseNull(accessGroups
);
1604 return SecEntitlementError(error
);
1607 SecSignpostStart(SecSignpostSecItemCopyMatching
);
1609 if (client
->canAccessNetworkExtensionAccessGroups
) {
1610 CFDataRef persistentRef
= CFDictionaryGetValue(query
, kSecValuePersistentRef
);
1611 CFStringRef itemClass
= NULL
;
1612 sqlite_int64 itemRowID
= 0;
1613 if (persistentRef
&& _SecItemParsePersistentRef(persistentRef
, &itemClass
, &itemRowID
, NULL
)) {
1614 CFStringRef accessGroup
= CopyAccessGroupForRowID(itemRowID
, itemClass
);
1615 if (accessGroup
&& CFStringHasSuffix(accessGroup
, kSecNetworkExtensionAccessGroupSuffix
) && !CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), accessGroup
)) {
1616 CFMutableArrayRef mutableAccessGroups
= CFArrayCreateMutableCopy(NULL
, 0, accessGroups
);
1617 CFArrayAppendValue(mutableAccessGroups
, accessGroup
);
1618 CFReleaseNull(accessGroups
);
1619 accessGroups
= mutableAccessGroups
;
1621 CFReleaseNull(accessGroup
);
1625 if (CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), CFSTR("*"))) {
1626 /* Having the special accessGroup "*" allows access to all accessGroups. */
1627 CFReleaseNull(accessGroups
);
1631 Query
*q
= query_create_with_limit(query
, client
->musr
, 1, error
);
1633 CFStringRef agrp
= CFDictionaryGetValue(q
->q_item
, kSecAttrAccessGroup
);
1635 if (accessGroupsAllows(accessGroups
, agrp
, client
)) {
1636 const void *val
= agrp
;
1637 CFReleaseNull(accessGroups
);
1638 accessGroups
= CFArrayCreate(0, &val
, 1, &kCFTypeArrayCallBacks
);
1640 (void)SecEntitlementErrorForExplicitAccessGroup(agrp
, accessGroups
, error
);
1641 CFReleaseNull(accessGroups
);
1642 query_destroy(q
, NULL
);
1647 if (accessGroups
!= NULL
) {
1648 // On iOS, drop 'com.apple.token' AG from allowed accessGroups, to avoid inserting token elements into
1649 // unsuspecting application's keychain. If the application on iOS wants to access token items, it needs
1650 // explicitly specify kSecAttrAccessGroup=kSecAttrAccessGroupToken in its query.
1651 CFMutableArrayRef mutableGroups
= CFArrayCreateMutableCopy(kCFAllocatorDefault
, 0, accessGroups
);
1652 CFArrayRemoveAllValue(mutableGroups
, kSecAttrAccessGroupToken
);
1653 CFReleaseNull(accessGroups
);
1654 accessGroups
= mutableGroups
;
1659 #if TARGET_OS_IPHONE
1660 if (q
->q_sync_bubble
&& client
->inMultiUser
) {
1661 CFReleaseNull(q
->q_musrView
);
1662 q
->q_musrView
= SecMUSRCreateSyncBubbleUserUUID(q
->q_sync_bubble
);
1663 } else if (client
->inMultiUser
&& client
->isNetworkExtension
) {
1664 CFReleaseNull(q
->q_musrView
);
1665 q
->q_musrView
= SecMUSRCreateBothUserAndSystemUUID(client
->uid
);
1666 } else if (q
->q_system_keychain
&& client
->inMultiUser
) {
1667 CFReleaseNull(q
->q_musrView
);
1668 q
->q_musrView
= SecMUSRCopySystemKeychainUUID();
1670 q
->q_system_keychain
= false;
1674 query_set_caller_access_groups(q
, accessGroups
);
1676 /* Sanity check the query. */
1677 if (q
->q_system_keychain
&& !client
->allowSystemKeychain
) {
1678 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for system keychain"));
1679 } else if (q
->q_sync_bubble
&& !client
->allowSyncBubbleKeychain
) {
1680 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for syncbubble keychain"));
1681 } else if (q
->q_system_keychain
&& q
->q_sync_bubble
) {
1682 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("can't do both system and syncbubble keychain"));
1683 } else if (q
->q_use_item_list
) {
1684 ok
= SecError(errSecUseItemListUnsupported
, error
, CFSTR("use item list unsupported"));
1685 } else if (q
->q_match_issuer
&& ((q
->q_class
!= cert_class()) &&
1686 (q
->q_class
!= identity_class()))) {
1687 ok
= SecError(errSecUnsupportedOperation
, error
, CFSTR("unsupported match attribute"));
1688 } else if (q
->q_match_policy
&& ((q
->q_class
!= cert_class()) &&
1689 (q
->q_class
!= identity_class()))) {
1690 ok
= SecError(errSecUnsupportedOperation
, error
, CFSTR("unsupported kSecMatchPolicy attribute"));
1691 } else if (q
->q_return_type
!= 0 && result
== NULL
) {
1692 ok
= SecError(errSecReturnMissingPointer
, error
, CFSTR("missing pointer"));
1693 } else if (!q
->q_error
) {
1694 ok
= kc_with_dbt(false, error
, ^(SecDbConnectionRef dbt
) {
1695 return s3dl_copy_matching(dbt
, q
, result
, accessGroups
, error
);
1699 if (!query_destroy(q
, error
))
1702 CFReleaseNull(accessGroups
);
1704 SecSignpostStop(SecSignpostSecItemCopyMatching
);
1710 _SecItemCopyMatching(CFDictionaryRef query
, SecurityClient
*client
, CFTypeRef
*result
, CFErrorRef
*error
) {
1711 return SecItemServerCopyMatching(query
, result
, client
, error
);
1714 #if TARGET_OS_IPHONE
1716 SecItemSynchronizable(CFDictionaryRef query
)
1718 bool result
= false;
1719 CFTypeRef value
= CFDictionaryGetValue(query
, kSecAttrSynchronizable
);
1720 if (isBoolean(value
))
1721 return CFBooleanGetValue(value
);
1722 else if (isNumber(value
)) {
1724 (void)CFNumberGetValue(value
, kCFNumberSInt32Type
, &number
);
1733 SecurityClientCopyWritableAccessGroups(SecurityClient
*client
) {
1734 if (client
== NULL
|| client
->accessGroups
== NULL
) {
1737 CFIndex count
= CFArrayGetCount(client
->accessGroups
);
1738 if (CFArrayContainsValue(client
->accessGroups
, CFRangeMake(0, count
), kSecAttrAccessGroupToken
)) {
1739 CFMutableArrayRef writableGroups
= CFArrayCreateMutableCopy(kCFAllocatorDefault
, 0, client
->accessGroups
);
1740 CFArrayRemoveAllValue(writableGroups
, kSecAttrAccessGroupToken
);
1741 return writableGroups
;
1743 return CFRetainSafe(client
->accessGroups
);
1748 /* AUDIT[securityd](done):
1749 attributes (ok) is a caller provided dictionary, only its cf type has
1753 _SecItemAdd(CFDictionaryRef attributes
, SecurityClient
*client
, CFTypeRef
*result
, CFErrorRef
*error
)
1755 CFArrayRef accessGroups
= SecurityClientCopyWritableAccessGroups(client
);
1759 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
))) {
1760 CFReleaseNull(accessGroups
);
1761 return SecEntitlementError(error
);
1764 SecSignpostStart(SecSignpostSecItemAdd
);
1766 Query
*q
= query_create_with_limit(attributes
, client
->musr
, 0, error
);
1768 /* Access group sanity checking. */
1769 CFStringRef agrp
= (CFStringRef
)CFDictionaryGetValue(attributes
,
1770 kSecAttrAccessGroup
);
1772 /* Having the special accessGroup "*" allows access to all accessGroups. */
1773 if (CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), CFSTR("*")))
1774 CFReleaseNull(accessGroups
);
1777 /* The user specified an explicit access group, validate it. */
1778 if (!accessGroupsAllows(accessGroups
, agrp
, client
))
1779 ok
= SecEntitlementErrorForExplicitAccessGroup(agrp
, accessGroups
, error
);
1781 agrp
= (CFStringRef
)CFArrayGetValueAtIndex(client
->accessGroups
, 0);
1783 /* We are using an implicit access group, add it as if the user
1784 specified it as an attribute. */
1785 query_add_attribute(kSecAttrAccessGroup
, agrp
, q
);
1788 if (SecPLShouldLogRegisteredEvent(CFSTR("SecItem"))) {
1789 CFDictionaryRef dict
= CFDictionaryCreateForCFTypes(NULL
, CFSTR("operation"), CFSTR("add"), CFSTR("AccessGroup"), agrp
, NULL
);
1791 SecPLLogRegisteredEvent(CFSTR("SecItem"), dict
);
1795 #if TARGET_OS_IPHONE
1796 if (q
->q_system_keychain
&& client
->inMultiUser
) {
1797 CFReleaseNull(q
->q_musrView
);
1798 q
->q_musrView
= SecMUSRCopySystemKeychainUUID();
1800 q
->q_system_keychain
= false;
1802 query_add_attribute_with_desc(&v8musr
, q
->q_musrView
, q
);
1806 query_ensure_access_control(q
, agrp
);
1809 void (^add_sync_callback
)(bool, CFErrorRef
) = CFDictionaryGetValue(attributes
, CFSTR("f_ckkscallback"));
1810 if(add_sync_callback
) {
1811 // The existence of this callback indicates that we need a predictable UUID for this item.
1812 q
->q_uuid_from_primary_key
= true;
1813 q
->q_add_sync_callback
= add_sync_callback
;
1817 if (q
->q_system_keychain
&& !client
->allowSystemKeychain
) {
1818 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for system keychain"));
1819 } else if (q
->q_sync_bubble
&& !client
->allowSyncBubbleKeychain
) {
1820 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for syncbubble keychain"));
1821 #if TARGET_OS_IPHONE
1822 } else if (q
->q_system_keychain
&& SecItemSynchronizable(attributes
) && !client
->inMultiUser
) {
1823 ok
= SecError(errSecInvalidKey
, error
, CFSTR("Can't store system keychain and synchronizable"));
1825 } else if (q
->q_row_id
|| q
->q_token_object_id
) {
1826 ok
= SecError(errSecValuePersistentRefUnsupported
, error
, CFSTR("q_row_id")); // TODO: better error string
1827 } else if (!q
->q_error
) {
1828 ok
= kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
){
1829 return kc_transaction(dbt
, error
, ^{
1830 query_pre_add(q
, true);
1831 return s3dl_query_add(dbt
, q
, result
, error
);
1836 ok
= query_notify_and_destroy(q
, ok
, error
);
1840 CFReleaseNull(accessGroups
);
1842 SecSignpostStop(SecSignpostSecItemAdd
);
1847 /* AUDIT[securityd](done):
1848 query (ok) and attributesToUpdate (ok) are a caller provided dictionaries,
1849 only their cf types have been checked.
1852 _SecItemUpdate(CFDictionaryRef query
, CFDictionaryRef attributesToUpdate
,
1853 SecurityClient
*client
, CFErrorRef
*error
)
1855 CFArrayRef accessGroups
= SecurityClientCopyWritableAccessGroups(client
);
1858 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
))) {
1859 CFReleaseNull(accessGroups
);
1860 return SecEntitlementError(error
);
1863 // Queries using implicit access groups which only find items that're inaccessible yield errSecItemNotFound,
1864 // but we can pre-emptively shut down queries which are clearly illegal
1865 CFTypeRef q_agrp
= CFDictionaryGetValue(query
, kSecAttrAccessGroup
);
1866 if (q_agrp
&& !accessGroupsAllows(accessGroups
, q_agrp
, client
)) {
1867 SecEntitlementErrorForExplicitAccessGroup(q_agrp
, accessGroups
, error
);
1868 CFReleaseSafe(accessGroups
);
1872 SecSignpostStart(SecSignpostSecItemUpdate
);
1874 if (SecPLShouldLogRegisteredEvent(CFSTR("SecItem"))) {
1875 CFTypeRef agrp
= CFArrayGetValueAtIndex(accessGroups
, 0);
1876 CFDictionaryRef dict
= CFDictionaryCreateForCFTypes(NULL
, CFSTR("operation"), CFSTR("update"), CFSTR("AccessGroup"), agrp
, NULL
);
1878 SecPLLogRegisteredEvent(CFSTR("SecItem"), dict
);
1883 if (CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), CFSTR("*"))) {
1884 /* Having the special accessGroup "*" allows access to all accessGroups. */
1885 CFReleaseNull(accessGroups
);
1889 Query
*q
= query_create_with_limit(query
, client
->musr
, kSecMatchUnlimited
, error
);
1894 #if TARGET_OS_IPHONE
1895 if (q
->q_system_keychain
&& client
->inMultiUser
) {
1896 CFReleaseNull(q
->q_musrView
);
1897 q
->q_musrView
= SecMUSRCopySystemKeychainUUID();
1899 q
->q_system_keychain
= false;
1903 /* Sanity check the query. */
1904 query_set_caller_access_groups(q
, accessGroups
);
1905 if (q
->q_system_keychain
&& !client
->allowSystemKeychain
) {
1906 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for system keychain"));
1907 } else if (q
->q_sync_bubble
&& !client
->allowSyncBubbleKeychain
) {
1908 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for syncbubble keychain"));
1909 #if TARGET_OS_IPHONE
1910 } else if (q
->q_system_keychain
&& SecItemSynchronizable(attributesToUpdate
) && !client
->inMultiUser
) {
1911 ok
= SecError(errSecInvalidKey
, error
, CFSTR("Can't update an system keychain item with synchronizable"));
1913 } else if (q
->q_use_item_list
) {
1914 ok
= SecError(errSecUseItemListUnsupported
, error
, CFSTR("use item list not supported"));
1915 } else if (q
->q_return_type
& kSecReturnDataMask
) {
1916 /* Update doesn't return anything so don't ask for it. */
1917 ok
= SecError(errSecReturnDataUnsupported
, error
, CFSTR("return data not supported by update"));
1918 } else if (q
->q_return_type
& kSecReturnAttributesMask
) {
1919 ok
= SecError(errSecReturnAttributesUnsupported
, error
, CFSTR("return attributes not supported by update"));
1920 } else if (q
->q_return_type
& kSecReturnRefMask
) {
1921 ok
= SecError(errSecReturnRefUnsupported
, error
, CFSTR("return ref not supported by update"));
1922 } else if (q
->q_return_type
& kSecReturnPersistentRefMask
) {
1923 ok
= SecError(errSecReturnPersistentRefUnsupported
, error
, CFSTR("return persistent ref not supported by update"));
1925 /* Access group sanity checking. */
1926 CFStringRef agrp
= (CFStringRef
)CFDictionaryGetValue(attributesToUpdate
,
1927 kSecAttrAccessGroup
);
1929 /* The user is attempting to modify the access group column,
1930 validate it to make sure the new value is allowable. */
1931 if (!accessGroupsAllows(accessGroups
, agrp
, client
)) {
1932 secerror("Cannot update keychain item to access group %@", agrp
);
1933 ok
= SecEntitlementErrorForExplicitAccessGroup(agrp
, accessGroups
, error
);
1939 ok
= kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
1940 return kc_transaction(dbt
, error
, ^{
1941 return s3dl_query_update(dbt
, q
, attributesToUpdate
, accessGroups
, error
);
1946 ok
= query_notify_and_destroy(q
, ok
, error
);
1948 CFReleaseNull(accessGroups
);
1950 SecSignpostStop(SecSignpostSecItemUpdate
);
1956 /* AUDIT[securityd](done):
1957 query (ok) is a caller provided dictionary, only its cf type has been checked.
1960 _SecItemDelete(CFDictionaryRef query
, SecurityClient
*client
, CFErrorRef
*error
)
1962 CFArrayRef accessGroups
= SecurityClientCopyWritableAccessGroups(client
);
1965 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
))) {
1966 CFReleaseNull(accessGroups
);
1967 return SecEntitlementError(error
);
1970 CFTypeRef q_agrp
= CFDictionaryGetValue(query
, kSecAttrAccessGroup
);
1971 if (q_agrp
&& !accessGroupsAllows(accessGroups
, q_agrp
, client
)) {
1972 SecEntitlementErrorForExplicitAccessGroup(q_agrp
, accessGroups
, error
);
1973 CFReleaseSafe(accessGroups
);
1977 SecSignpostStart(SecSignpostSecItemDelete
);
1979 if (SecPLShouldLogRegisteredEvent(CFSTR("SecItem"))) {
1980 CFTypeRef agrp
= CFArrayGetValueAtIndex(accessGroups
, 0);
1981 CFDictionaryRef dict
= CFDictionaryCreateForCFTypes(NULL
, CFSTR("operation"), CFSTR("delete"), CFSTR("AccessGroup"), agrp
, NULL
);
1983 SecPLLogRegisteredEvent(CFSTR("SecItem"), dict
);
1988 if (CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), CFSTR("*"))) {
1989 /* Having the special accessGroup "*" allows access to all accessGroups. */
1990 CFReleaseNull(accessGroups
);
1993 Query
*q
= query_create_with_limit(query
, client
->musr
, kSecMatchUnlimited
, error
);
1996 #if TARGET_OS_IPHONE
1997 if (q
->q_system_keychain
&& client
->inMultiUser
) {
1998 CFReleaseNull(q
->q_musrView
);
1999 q
->q_musrView
= SecMUSRCopySystemKeychainUUID();
2001 q
->q_system_keychain
= false;
2005 query_set_caller_access_groups(q
, accessGroups
);
2006 /* Sanity check the query. */
2007 if (q
->q_system_keychain
&& !client
->allowSystemKeychain
) {
2008 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for system keychain"));
2009 } else if (q
->q_sync_bubble
&& !client
->allowSyncBubbleKeychain
) {
2010 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for syncbubble keychain"));
2011 } else if (q
->q_limit
!= kSecMatchUnlimited
) {
2012 ok
= SecError(errSecMatchLimitUnsupported
, error
, CFSTR("match limit not supported by delete"));
2013 } else if (query_match_count(q
) != 0) {
2014 ok
= SecError(errSecItemMatchUnsupported
, error
, CFSTR("match not supported by delete"));
2015 } else if (q
->q_ref
) {
2016 ok
= SecError(errSecValueRefUnsupported
, error
, CFSTR("value ref not supported by delete"));
2017 } else if (q
->q_row_id
&& query_attr_count(q
)) {
2018 ok
= SecError(errSecItemIllegalQuery
, error
, CFSTR("rowid and other attributes are mutually exclusive"));
2019 } else if (q
->q_token_object_id
&& query_attr_count(q
) != 1) {
2020 ok
= SecError(errSecItemIllegalQuery
, error
, CFSTR("token persistent ref and other attributes are mutually exclusive"));
2022 ok
= kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
2023 return kc_transaction(dbt
, error
, ^{
2024 return s3dl_query_delete(dbt
, q
, accessGroups
, error
);
2028 ok
= query_notify_and_destroy(q
, ok
, error
);
2032 CFReleaseNull(accessGroups
);
2034 SecSignpostStop(SecSignpostSecItemDelete
);
2039 static bool SecItemDeleteTokenItems(SecDbConnectionRef dbt
, CFTypeRef classToDelete
, CFTypeRef tokenID
, CFArrayRef accessGroups
, SecurityClient
*client
, CFErrorRef
*error
) {
2040 CFTypeRef keys
[] = { kSecClass
, kSecAttrTokenID
};
2041 CFTypeRef values
[] = { classToDelete
, tokenID
};
2043 CFDictionaryRef query
= CFDictionaryCreate(kCFAllocatorDefault
, keys
, values
, 2, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2044 Query
*q
= query_create_with_limit(query
, client
->musr
, kSecMatchUnlimited
, error
);
2048 query_set_caller_access_groups(q
, accessGroups
);
2049 ok
= s3dl_query_delete(dbt
, q
, accessGroups
, error
);
2050 ok
= query_notify_and_destroy(q
, ok
, error
);
2058 static bool SecItemAddTokenItem(SecDbConnectionRef dbt
, CFDictionaryRef attributes
, CFArrayRef accessGroups
, SecurityClient
*client
, CFErrorRef
*error
) {
2060 Query
*q
= query_create_with_limit(attributes
, client
->musr
, 0, error
);
2062 CFStringRef agrp
= kSecAttrAccessGroupToken
;
2063 query_add_attribute(kSecAttrAccessGroup
, agrp
, q
);
2066 query_ensure_access_control(q
, agrp
);
2067 if (q
->q_system_keychain
&& !client
->allowSystemKeychain
) {
2068 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for system keychain"));
2069 } else if (q
->q_sync_bubble
&& !client
->allowSyncBubbleKeychain
) {
2070 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for syncbubble keychain"));
2071 } else if (q
->q_row_id
|| q
->q_token_object_id
) {
2072 ok
= SecError(errSecValuePersistentRefUnsupported
, error
, CFSTR("q_row_id")); // TODO: better error string
2073 } else if (!q
->q_error
) {
2074 query_pre_add(q
, true);
2075 ok
= s3dl_query_add(dbt
, q
, NULL
, error
);
2078 ok
= query_notify_and_destroy(q
, ok
, error
);
2085 bool _SecItemUpdateTokenItems(CFStringRef tokenID
, CFArrayRef items
, SecurityClient
*client
, CFErrorRef
*error
) {
2087 CFArrayRef accessGroups
= client
->accessGroups
;
2089 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
))) {
2090 return SecEntitlementError(error
);
2093 ok
= kc_with_dbt(true, error
, ^bool (SecDbConnectionRef dbt
) {
2094 return kc_transaction(dbt
, error
, ^bool {
2096 const CFTypeRef classToDelete
[] = { kSecClassGenericPassword
, kSecClassInternetPassword
, kSecClassCertificate
, kSecClassKey
};
2097 for (size_t i
= 0; i
< sizeof(classToDelete
) / sizeof(classToDelete
[0]); ++i
) {
2098 SecItemDeleteTokenItems(dbt
, classToDelete
[i
], tokenID
, accessGroups
, client
, NULL
);
2101 for (CFIndex i
= 0; i
< CFArrayGetCount(items
); ++i
) {
2102 if (!SecItemAddTokenItem(dbt
, CFArrayGetValueAtIndex(items
, i
), accessGroups
, client
, error
))
2108 const CFTypeRef classToDelete
[] = { kSecClassGenericPassword
, kSecClassInternetPassword
, kSecClassCertificate
, kSecClassKey
};
2109 bool deleted
= true;
2110 for (size_t i
= 0; i
< sizeof(classToDelete
) / sizeof(classToDelete
[0]); ++i
) {
2111 if (!SecItemDeleteTokenItems(dbt
, classToDelete
[i
], tokenID
, accessGroups
, client
, error
) && error
&& CFErrorGetCode(*error
) != errSecItemNotFound
) {
2115 else if (error
&& *error
) {
2116 CFReleaseNull(*error
);
2127 static bool deleteNonSysboundItemsForItemClass(SecDbConnectionRef dbt
, SecDbClass
const* class, CFErrorRef
* error
) {
2128 CFMutableDictionaryRef query
= CFDictionaryCreateMutableForCFTypes(NULL
);
2129 CFDictionaryAddValue(query
, kSecMatchLimit
, kSecMatchLimitAll
);
2131 __block CFErrorRef localError
= NULL
;
2132 SecDbQueryRef q
= query_create(class, NULL
, query
, &localError
);
2133 if (q
== NULL
) { // illegal query or out of memory
2134 secerror("SecItemServerDeleteAll: aborting because failed to initialize Query: %@", localError
);
2137 SecDbItemSelect(q
, dbt
, &localError
, ^bool(const SecDbAttr
*attr
) {
2138 return (attr
->flags
& kSecDbInFlag
) && !CFEqual(attr
->name
, CFSTR("data"));
2139 }, NULL
, NULL
, NULL
,
2140 ^(SecDbItemRef item
, bool *stop
) {
2141 if (!SecItemIsSystemBound(item
->attributes
, class, false) &&
2142 !CFEqual(CFDictionaryGetValue(item
->attributes
, kSecAttrAccessGroup
), CFSTR("com.apple.bluetooth")))
2144 SecDbItemDelete(item
, dbt
, kCFBooleanFalse
, &localError
);
2147 query_destroy(q
, &localError
);
2151 CFReleaseNull(*error
);
2152 *error
= localError
;
2154 CFReleaseNull(localError
);
2161 // Delete all the items except sysbound ones because horrible things happen if you do, like bluetooth devices unpairing
2163 SecItemServerDeleteAll(CFErrorRef
*error
) {
2164 secerror("SecItemServerDeleteAll");
2165 return kc_with_dbt(true, error
, ^bool (SecDbConnectionRef dbt
) {
2166 return (kc_transaction(dbt
, error
, ^bool {
2168 ok
&= SecDbExec(dbt
, CFSTR("DELETE FROM genp WHERE sync=1;"), error
);
2169 ok
&= SecDbExec(dbt
, CFSTR("DELETE FROM inet WHERE sync=1;"), error
);
2170 ok
&= SecDbExec(dbt
, CFSTR("DELETE FROM cert WHERE sync=1;"), error
);
2171 ok
&= SecDbExec(dbt
, CFSTR("DELETE FROM keys WHERE sync=1;"), error
);
2173 ok
&= deleteNonSysboundItemsForItemClass(dbt
, genp_class(), error
);
2174 ok
&= deleteNonSysboundItemsForItemClass(dbt
, inet_class(), error
);
2175 ok
&= deleteNonSysboundItemsForItemClass(dbt
, cert_class(), error
);
2176 ok
&= deleteNonSysboundItemsForItemClass(dbt
, keys_class(), error
);
2179 }) && SecDbExec(dbt
, CFSTR("VACUUM;"), error
));
2184 _SecItemDeleteAll(CFErrorRef
*error
) {
2185 return SecItemServerDeleteAll(error
);
2189 _SecItemServerDeleteAllWithAccessGroups(CFArrayRef accessGroups
, SecurityClient
*client
, CFErrorRef
*error
)
2191 __block
bool ok
= true;
2192 static dispatch_once_t onceToken
;
2193 static CFSetRef illegalAccessGroups
= NULL
;
2195 dispatch_once(&onceToken
, ^{
2196 const CFStringRef values
[] = {
2199 CFSTR("com.apple.security.sos"),
2200 CFSTR("lockdown-identities"),
2202 illegalAccessGroups
= CFSetCreate(NULL
, (const void **)values
, sizeof(values
)/sizeof(values
[0]), &kCFTypeSetCallBacks
);
2205 static CFTypeRef qclasses
[] = {
2211 // strange construction needed for schema indirection
2212 static dispatch_once_t qclassesOnceToken
;
2213 dispatch_once(&qclassesOnceToken
, ^{
2214 qclasses
[0] = inet_class();
2215 qclasses
[1] = genp_class();
2216 qclasses
[2] = keys_class();
2217 qclasses
[3] = cert_class();
2220 require_action_quiet(isArray(accessGroups
), fail
,
2222 SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType
, sSecXPCErrorDomain
, NULL
, error
, NULL
, CFSTR("accessGroups not CFArray, got %@"), accessGroups
));
2224 // TODO: whitelist instead? look for dev IDs like 7123498YQX.com.somedev.app
2226 require_action(CFArrayGetCount(accessGroups
) != 0, fail
,
2228 SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType
, sSecXPCErrorDomain
, NULL
, error
, NULL
, CFSTR("accessGroups e empty")));
2231 // Pre-check accessGroups for prohibited values
2232 CFArrayForEach(accessGroups
, ^(const void *value
) {
2233 CFStringRef agrp
= (CFStringRef
)value
;
2235 if (!isString(agrp
)) {
2236 SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType
, sSecXPCErrorDomain
, NULL
, error
, NULL
,
2237 CFSTR("access not a string: %@"), agrp
);
2239 } else if (CFSetContainsValue(illegalAccessGroups
, agrp
)) {
2240 SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType
, sSecXPCErrorDomain
, NULL
, error
, NULL
,
2241 CFSTR("illegal access group: %@"), accessGroups
);
2247 ok
= kc_with_dbt(true, error
, ^bool(SecDbConnectionRef dbt
) {
2248 return kc_transaction(dbt
, error
, ^bool {
2249 CFErrorRef localError
= NULL
;
2253 for (n
= 0; n
< sizeof(qclasses
)/sizeof(qclasses
[0]) && ok1
; n
++) {
2256 q
= query_create(qclasses
[n
], client
->musr
, NULL
, error
);
2259 (void)s3dl_query_delete(dbt
, q
, accessGroups
, &localError
);
2261 query_destroy(q
, error
);
2262 CFReleaseNull(localError
);
2265 }) && SecDbExec(dbt
, CFSTR("VACUUM"), error
);
2274 // MARK: Shared web credentials
2276 #if SHAREDWEBCREDENTIALS
2279 #define SEC_CONST_DECL(k,v) const CFStringRef k = CFSTR(v);
2281 SEC_CONST_DECL (kSecSafariAccessGroup
, "com.apple.cfnetwork");
2282 SEC_CONST_DECL (kSecSafariDefaultComment
, "default");
2283 SEC_CONST_DECL (kSecSafariPasswordsNotSaved
, "Passwords not saved");
2284 SEC_CONST_DECL (kSecSharedCredentialUrlScheme
, "https://");
2285 SEC_CONST_DECL (kSecSharedWebCredentialsService
, "webcredentials");
2287 #if !TARGET_OS_SIMULATOR
2289 _SecAppDomainApprovalStatus(CFStringRef appID
, CFStringRef fqdn
, CFErrorRef
*error
)
2291 __block SWCFlags flags
= kSWCFlags_None
;
2294 secnotice("swc", "Application %@ is requesting approval for %@", appID
, fqdn
);
2296 dispatch_semaphore_t semaphore
= dispatch_semaphore_create(0);
2297 if (semaphore
== NULL
)
2300 status
= SWCCheckService(kSecSharedWebCredentialsService
, appID
, fqdn
, ^void (OSStatus inStatus
, SWCFlags inFlags
, CFDictionaryRef inDetails
)
2302 if (inStatus
== 0) {
2305 secerror("SWCCheckService failed with %d", (int)inStatus
);
2307 dispatch_semaphore_signal(semaphore
);
2311 dispatch_semaphore_wait(semaphore
, DISPATCH_TIME_FOREVER
);
2313 secerror("SWCCheckService: failed to queue");
2315 dispatch_release(semaphore
);
2318 if (!(flags
& kSWCFlag_SiteApproved
)) {
2319 SecError(errSecAuthFailed
, error
, CFSTR("\"%@\" failed to approve \"%@\""), fqdn
, appID
);
2320 } else if (flags
& kSWCFlag_UserDenied
) {
2321 SecError(errSecAuthFailed
, error
, CFSTR("User denied access to \"%@\" by \"%@\""), fqdn
, appID
);
2328 _SecEntitlementContainsDomainForService(CFArrayRef domains
, CFStringRef domain
, CFStringRef service
)
2330 bool result
= false;
2331 CFIndex idx
, count
= (domains
) ? CFArrayGetCount(domains
) : (CFIndex
) 0;
2332 if (!count
|| !domain
|| !service
) {
2335 for (idx
=0; idx
< count
; idx
++) {
2336 CFStringRef str
= (CFStringRef
) CFArrayGetValueAtIndex(domains
, idx
);
2337 if (str
&& CFStringHasPrefix(str
, kSecSharedWebCredentialsService
)) {
2338 CFIndex prefix_len
= CFStringGetLength(kSecSharedWebCredentialsService
)+1;
2339 CFIndex substr_len
= CFStringGetLength(str
) - prefix_len
;
2340 CFRange range
= { prefix_len
, substr_len
};
2341 CFStringRef substr
= CFStringCreateWithSubstring(kCFAllocatorDefault
, str
, range
);
2342 if (substr
&& CFEqual(substr
, domain
)) {
2345 CFReleaseSafe(substr
);
2353 #endif /* !TARGET_OS_SIMULATOR */
2356 _SecAddNegativeWebCredential(SecurityClient
*client
, CFStringRef fqdn
, CFStringRef appID
, bool forSafari
)
2358 #if !TARGET_OS_SIMULATOR
2359 bool result
= false;
2360 if (!fqdn
) { return result
; }
2362 // update our database
2363 CFRetainSafe(appID
);
2365 if (0 == SWCSetServiceFlags(kSecSharedWebCredentialsService
, appID
, fqdn
, kSWCFlag_ExternalMask
, kSWCFlag_UserDenied
,
2366 ^void(OSStatus inStatus
, SWCFlags inNewFlags
){
2367 CFReleaseSafe(appID
);
2368 CFReleaseSafe(fqdn
);
2373 else // didn't queue the block
2375 CFReleaseSafe(appID
);
2376 CFReleaseSafe(fqdn
);
2379 if (!forSafari
) { return result
; }
2381 // below this point: create a negative Safari web credential item
2383 CFMutableDictionaryRef attrs
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2384 if (!attrs
) { return result
; }
2386 CFErrorRef error
= NULL
;
2387 CFStringRef accessGroup
= CFSTR("*");
2388 SecurityClient swcclient
= {
2390 .accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&accessGroup
, 1, &kCFTypeArrayCallBacks
),
2391 .allowSystemKeychain
= false,
2392 .allowSyncBubbleKeychain
= false,
2393 .isNetworkExtension
= false,
2394 .musr
= client
->musr
,
2397 CFDictionaryAddValue(attrs
, kSecClass
, kSecClassInternetPassword
);
2398 CFDictionaryAddValue(attrs
, kSecAttrAccessGroup
, kSecSafariAccessGroup
);
2399 CFDictionaryAddValue(attrs
, kSecAttrAuthenticationType
, kSecAttrAuthenticationTypeHTMLForm
);
2400 CFDictionaryAddValue(attrs
, kSecAttrProtocol
, kSecAttrProtocolHTTPS
);
2401 CFDictionaryAddValue(attrs
, kSecAttrServer
, fqdn
);
2402 CFDictionaryAddValue(attrs
, kSecAttrSynchronizable
, kCFBooleanTrue
);
2404 (void)_SecItemDelete(attrs
, &swcclient
, &error
);
2405 CFReleaseNull(error
);
2407 CFDictionaryAddValue(attrs
, kSecAttrAccount
, kSecSafariPasswordsNotSaved
);
2408 CFDictionaryAddValue(attrs
, kSecAttrComment
, kSecSafariDefaultComment
);
2410 CFStringRef label
= CFStringCreateWithFormat(kCFAllocatorDefault
,
2411 NULL
, CFSTR("%@ (%@)"), fqdn
, kSecSafariPasswordsNotSaved
);
2413 CFDictionaryAddValue(attrs
, kSecAttrLabel
, label
);
2414 CFReleaseSafe(label
);
2418 CFDataRef data
= CFDataCreate(kCFAllocatorDefault
, &space
, 1);
2420 CFDictionarySetValue(attrs
, kSecValueData
, data
);
2421 CFReleaseSafe(data
);
2424 CFTypeRef addResult
= NULL
;
2425 result
= _SecItemAdd(attrs
, &swcclient
, &addResult
, &error
);
2427 CFReleaseSafe(addResult
);
2428 CFReleaseSafe(error
);
2429 CFReleaseSafe(attrs
);
2430 CFReleaseSafe(swcclient
.accessGroups
);
2438 /* Specialized version of SecItemAdd for shared web credentials */
2440 _SecAddSharedWebCredential(CFDictionaryRef attributes
,
2441 SecurityClient
*client
,
2442 const audit_token_t
*clientAuditToken
,
2449 SecurityClient swcclient
= {};
2451 CFStringRef fqdn
= CFRetainSafe(CFDictionaryGetValue(attributes
, kSecAttrServer
));
2452 CFStringRef account
= CFDictionaryGetValue(attributes
, kSecAttrAccount
);
2453 #if TARGET_OS_IOS && !TARGET_OS_BRIDGE
2454 CFStringRef password
= CFDictionaryGetValue(attributes
, kSecSharedPassword
);
2456 CFStringRef password
= CFDictionaryGetValue(attributes
, CFSTR("spwd"));
2458 CFStringRef accessGroup
= CFSTR("*");
2459 CFMutableDictionaryRef query
= NULL
, attrs
= NULL
;
2463 // check autofill enabled status
2464 if (!swca_autofill_enabled(clientAuditToken
)) {
2465 SecError(errSecBadReq
, error
, CFSTR("Autofill is not enabled in Safari settings"));
2469 // parse fqdn with CFURL here, since it could be specified as domain:port
2471 CFStringRef urlStr
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%@%@"), kSecSharedCredentialUrlScheme
, fqdn
);
2473 CFURLRef url
= CFURLCreateWithString(kCFAllocatorDefault
, urlStr
, nil
);
2475 CFStringRef hostname
= CFURLCopyHostName(url
);
2477 CFReleaseSafe(fqdn
);
2479 port
= CFURLGetPortNumber(url
);
2483 CFReleaseSafe(urlStr
);
2488 SecError(errSecParam
, error
, CFSTR("No account provided"));
2492 SecError(errSecParam
, error
, CFSTR("No domain provided"));
2496 #if TARGET_OS_SIMULATOR
2497 secerror("app/site association entitlements not checked in Simulator");
2499 OSStatus status
= errSecMissingEntitlement
;
2500 // validate that fqdn is part of caller's shared credential domains entitlement
2502 SecError(status
, error
, CFSTR("Missing application-identifier entitlement"));
2505 if (_SecEntitlementContainsDomainForService(domains
, fqdn
, kSecSharedWebCredentialsService
)) {
2506 status
= errSecSuccess
;
2508 if (errSecSuccess
!= status
) {
2509 CFStringRef msg
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
,
2510 CFSTR("%@ not found in %@ entitlement"), fqdn
, kSecEntitlementAssociatedDomains
);
2512 msg
= CFRetain(CFSTR("Requested domain not found in entitlement"));
2514 SecError(status
, error
, CFSTR("%@"), msg
);
2520 #if TARGET_OS_SIMULATOR
2521 secerror("Ignoring app/site approval state in the Simulator.");
2523 // get approval status for this app/domain pair
2524 SWCFlags flags
= _SecAppDomainApprovalStatus(appID
, fqdn
, error
);
2525 if (!(flags
& kSWCFlag_SiteApproved
)) {
2530 // give ourselves access to see matching items for kSecSafariAccessGroup
2531 swcclient
.task
= NULL
;
2532 swcclient
.accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&accessGroup
, 1, &kCFTypeArrayCallBacks
);
2533 swcclient
.allowSystemKeychain
= false;
2534 swcclient
.musr
= client
->musr
;
2535 swcclient
.allowSystemKeychain
= false;
2536 swcclient
.allowSyncBubbleKeychain
= false;
2537 swcclient
.isNetworkExtension
= false;
2540 // create lookup query
2541 query
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2543 SecError(errSecAllocate
, error
, CFSTR("Unable to create query dictionary"));
2546 CFDictionaryAddValue(query
, kSecClass
, kSecClassInternetPassword
);
2547 CFDictionaryAddValue(query
, kSecAttrAccessGroup
, kSecSafariAccessGroup
);
2548 CFDictionaryAddValue(query
, kSecAttrAuthenticationType
, kSecAttrAuthenticationTypeHTMLForm
);
2549 CFDictionaryAddValue(query
, kSecAttrServer
, fqdn
);
2550 CFDictionaryAddValue(query
, kSecAttrSynchronizable
, kCFBooleanTrue
);
2552 // check for presence of Safari's negative entry ('passwords not saved')
2553 CFDictionarySetValue(query
, kSecAttrAccount
, kSecSafariPasswordsNotSaved
);
2554 ok
= _SecItemCopyMatching(query
, &swcclient
, result
, error
);
2555 if(result
) CFReleaseNull(*result
);
2556 if (error
) CFReleaseNull(*error
);
2558 SecError(errSecDuplicateItem
, error
, CFSTR("Item already exists for this server"));
2562 // now use the provided account (and optional port number, if one was present)
2563 CFDictionarySetValue(query
, kSecAttrAccount
, account
);
2564 if (port
< -1 || port
> 0) {
2565 SInt16 portValueShort
= (port
& 0xFFFF);
2566 CFNumberRef portNumber
= CFNumberCreate(NULL
, kCFNumberSInt16Type
, &portValueShort
);
2567 CFDictionaryAddValue(query
, kSecAttrPort
, portNumber
);
2568 CFReleaseSafe(portNumber
);
2571 // look up existing password
2572 CFDictionaryAddValue(query
, kSecReturnData
, kCFBooleanTrue
);
2573 bool matched
= _SecItemCopyMatching(query
, &swcclient
, result
, error
);
2574 CFDictionaryRemoveValue(query
, kSecReturnData
);
2576 // found it, so this becomes either an "update password" or "delete password" operation
2577 bool update
= (password
!= NULL
);
2579 attrs
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2580 CFDataRef credential
= CFStringCreateExternalRepresentation(kCFAllocatorDefault
, password
, kCFStringEncodingUTF8
, 0);
2581 CFDictionaryAddValue(attrs
, kSecValueData
, credential
);
2582 bool samePassword
= result
&& *result
&& CFEqual(*result
, credential
);
2583 CFReleaseSafe(credential
);
2584 CFDictionaryAddValue(attrs
, kSecAttrComment
, kSecSafariDefaultComment
);
2586 ok
= samePassword
|| swca_confirm_operation(swca_update_request_id
, clientAuditToken
, query
, error
,
2587 ^void (CFStringRef confirm_fqdn
) {
2588 _SecAddNegativeWebCredential(client
, confirm_fqdn
, appID
, false);
2591 ok
= _SecItemUpdate(query
, attrs
, &swcclient
, error
);
2595 // confirm the delete
2596 // (per rdar://16676288 we always prompt, even if there was prior user approval)
2597 ok
= /*approved ||*/ swca_confirm_operation(swca_delete_request_id
, clientAuditToken
, query
, error
,
2598 ^void (CFStringRef confirm_fqdn
) {
2599 _SecAddNegativeWebCredential(client
, confirm_fqdn
, appID
, false);
2602 ok
= _SecItemDelete(query
, &swcclient
, error
);
2606 if(result
) CFReleaseNull(*result
);
2607 if(error
) CFReleaseNull(*error
);
2611 if (result
) CFReleaseNull(*result
);
2612 if (error
) CFReleaseNull(*error
);
2614 // password does not exist, so prepare to add it
2616 // a NULL password value removes the existing credential. Since we didn't find it, this is a no-op.
2621 CFStringRef label
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%@ (%@)"), fqdn
, account
);
2623 CFDictionaryAddValue(query
, kSecAttrLabel
, label
);
2624 CFReleaseSafe(label
);
2626 // NOTE: we always expect to use HTTPS for web forms.
2627 CFDictionaryAddValue(query
, kSecAttrProtocol
, kSecAttrProtocolHTTPS
);
2629 CFDataRef credential
= CFStringCreateExternalRepresentation(kCFAllocatorDefault
, password
, kCFStringEncodingUTF8
, 0);
2630 CFDictionarySetValue(query
, kSecValueData
, credential
);
2631 CFReleaseSafe(credential
);
2632 CFDictionarySetValue(query
, kSecAttrComment
, kSecSafariDefaultComment
);
2634 CFReleaseSafe(swcclient
.accessGroups
);
2635 swcclient
.accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&kSecSafariAccessGroup
, 1, &kCFTypeArrayCallBacks
);
2637 // mark the item as created by this function
2638 const int32_t creator_value
= 'swca';
2639 CFNumberRef creator
= CFNumberCreate(kCFAllocatorDefault
, kCFNumberSInt32Type
, &creator_value
);
2641 CFDictionarySetValue(query
, kSecAttrCreator
, creator
);
2642 CFReleaseSafe(creator
);
2646 ok
= swca_confirm_operation(swca_add_request_id
, clientAuditToken
, query
, error
, ^void (CFStringRef confirm_fqdn
) {
2647 _SecAddNegativeWebCredential(client
, confirm_fqdn
, appID
, false);
2651 ok
= _SecItemAdd(query
, &swcclient
, result
, error
);
2655 CFReleaseSafe(attrs
);
2656 CFReleaseSafe(query
);
2657 CFReleaseSafe(swcclient
.accessGroups
);
2658 CFReleaseSafe(fqdn
);
2662 /* Specialized version of SecItemCopyMatching for shared web credentials */
2664 _SecCopySharedWebCredential(CFDictionaryRef query
,
2665 SecurityClient
*client
,
2666 const audit_token_t
*clientAuditToken
,
2672 CFMutableArrayRef credentials
= NULL
;
2673 CFMutableArrayRef foundItems
= NULL
;
2674 CFMutableArrayRef fqdns
= NULL
;
2675 CFStringRef fqdn
= NULL
;
2676 CFStringRef account
= NULL
;
2680 require_quiet(result
, cleanup
);
2681 credentials
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
2682 foundItems
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
2683 fqdns
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
2685 // give ourselves access to see matching items for kSecSafariAccessGroup
2686 CFStringRef accessGroup
= CFSTR("*");
2687 SecurityClient swcclient
= {
2689 .accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&accessGroup
, 1, &kCFTypeArrayCallBacks
),
2690 .allowSystemKeychain
= false,
2691 .allowSyncBubbleKeychain
= false,
2692 .isNetworkExtension
= false,
2693 .musr
= client
->musr
,
2696 // On input, the query dictionary contains optional fqdn and account entries.
2697 fqdn
= CFDictionaryGetValue(query
, kSecAttrServer
);
2698 account
= CFDictionaryGetValue(query
, kSecAttrAccount
);
2700 // Check autofill enabled status
2701 if (!swca_autofill_enabled(clientAuditToken
)) {
2702 SecError(errSecBadReq
, error
, CFSTR("Autofill is not enabled in Safari settings"));
2706 // Check fqdn; if NULL, add domains from caller's entitlement.
2708 CFArrayAppendValue(fqdns
, fqdn
);
2711 CFIndex idx
, count
= CFArrayGetCount(domains
);
2712 for (idx
=0; idx
< count
; idx
++) {
2713 CFStringRef str
= (CFStringRef
) CFArrayGetValueAtIndex(domains
, idx
);
2714 // Parse the entry for our service label prefix
2715 if (str
&& CFStringHasPrefix(str
, kSecSharedWebCredentialsService
)) {
2716 CFIndex prefix_len
= CFStringGetLength(kSecSharedWebCredentialsService
)+1;
2717 CFIndex substr_len
= CFStringGetLength(str
) - prefix_len
;
2718 CFRange range
= { prefix_len
, substr_len
};
2719 fqdn
= CFStringCreateWithSubstring(kCFAllocatorDefault
, str
, range
);
2721 CFArrayAppendValue(fqdns
, fqdn
);
2729 count
= CFArrayGetCount(fqdns
);
2731 SecError(errSecParam
, error
, CFSTR("No domain provided"));
2735 // Aggregate search results for each domain
2736 for (idx
= 0; idx
< count
; idx
++) {
2737 CFMutableArrayRef items
= NULL
;
2738 CFMutableDictionaryRef attrs
= NULL
;
2739 fqdn
= (CFStringRef
) CFArrayGetValueAtIndex(fqdns
, idx
);
2743 // Parse the fqdn for a possible port specifier.
2745 CFStringRef urlStr
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%@%@"), kSecSharedCredentialUrlScheme
, fqdn
);
2747 CFURLRef url
= CFURLCreateWithString(kCFAllocatorDefault
, urlStr
, nil
);
2749 CFStringRef hostname
= CFURLCopyHostName(url
);
2751 CFReleaseSafe(fqdn
);
2753 port
= CFURLGetPortNumber(url
);
2757 CFReleaseSafe(urlStr
);
2761 #if TARGET_OS_SIMULATOR
2762 secerror("app/site association entitlements not checked in Simulator");
2764 OSStatus status
= errSecMissingEntitlement
;
2766 SecError(status
, error
, CFSTR("Missing application-identifier entitlement"));
2767 CFReleaseSafe(fqdn
);
2770 // validate that fqdn is part of caller's entitlement
2771 if (_SecEntitlementContainsDomainForService(domains
, fqdn
, kSecSharedWebCredentialsService
)) {
2772 status
= errSecSuccess
;
2774 if (errSecSuccess
!= status
) {
2775 CFStringRef msg
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
,
2776 CFSTR("%@ not found in %@ entitlement"), fqdn
, kSecEntitlementAssociatedDomains
);
2778 msg
= CFRetain(CFSTR("Requested domain not found in entitlement"));
2780 SecError(status
, error
, CFSTR("%@"), msg
);
2782 CFReleaseSafe(fqdn
);
2787 attrs
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2789 SecError(errSecAllocate
, error
, CFSTR("Unable to create query dictionary"));
2790 CFReleaseSafe(fqdn
);
2793 CFDictionaryAddValue(attrs
, kSecClass
, kSecClassInternetPassword
);
2794 CFDictionaryAddValue(attrs
, kSecAttrAccessGroup
, kSecSafariAccessGroup
);
2795 CFDictionaryAddValue(attrs
, kSecAttrProtocol
, kSecAttrProtocolHTTPS
);
2796 CFDictionaryAddValue(attrs
, kSecAttrAuthenticationType
, kSecAttrAuthenticationTypeHTMLForm
);
2797 CFDictionaryAddValue(attrs
, kSecAttrServer
, fqdn
);
2799 CFDictionaryAddValue(attrs
, kSecAttrAccount
, account
);
2801 if (port
< -1 || port
> 0) {
2802 SInt16 portValueShort
= (port
& 0xFFFF);
2803 CFNumberRef portNumber
= CFNumberCreate(NULL
, kCFNumberSInt16Type
, &portValueShort
);
2804 CFDictionaryAddValue(attrs
, kSecAttrPort
, portNumber
);
2805 CFReleaseSafe(portNumber
);
2807 CFDictionaryAddValue(attrs
, kSecAttrSynchronizable
, kCFBooleanTrue
);
2808 CFDictionaryAddValue(attrs
, kSecMatchLimit
, kSecMatchLimitAll
);
2809 CFDictionaryAddValue(attrs
, kSecReturnAttributes
, kCFBooleanTrue
);
2810 CFDictionaryAddValue(attrs
, kSecReturnData
, kCFBooleanTrue
);
2812 ok
= _SecItemCopyMatching(attrs
, &swcclient
, (CFTypeRef
*)&items
, error
);
2814 // ignore interim error since we have multiple domains to search
2815 CFReleaseNull(*error
);
2817 if (ok
&& items
&& CFGetTypeID(items
) == CFArrayGetTypeID()) {
2818 #if TARGET_OS_SIMULATOR
2819 secerror("Ignoring app/site approval state in the Simulator.");
2820 bool approved
= true;
2822 // get approval status for this app/domain pair
2823 SWCFlags flags
= _SecAppDomainApprovalStatus(appID
, fqdn
, error
);
2825 // ignore interim error since we have multiple domains to check
2826 CFReleaseNull(*error
);
2828 bool approved
= (flags
& kSWCFlag_SiteApproved
);
2831 CFArrayAppendArray(foundItems
, items
, CFRangeMake(0, CFArrayGetCount(items
)));
2834 CFReleaseSafe(items
);
2835 CFReleaseSafe(attrs
);
2836 CFReleaseSafe(fqdn
);
2839 // If matching credentials are found, the credentials provided to the completionHandler
2840 // will be a CFArrayRef containing CFDictionaryRef entries. Each dictionary entry will
2841 // contain the following pairs (see Security/SecItem.h):
2842 // key: kSecAttrServer value: CFStringRef (the website)
2843 // key: kSecAttrAccount value: CFStringRef (the account)
2844 // key: kSecSharedPassword value: CFStringRef (the password)
2846 // key: kSecAttrPort value: CFNumberRef (the port number, if non-standard for https)
2848 count
= CFArrayGetCount(foundItems
);
2849 for (idx
= 0; idx
< count
; idx
++) {
2850 CFDictionaryRef dict
= (CFDictionaryRef
) CFArrayGetValueAtIndex(foundItems
, idx
);
2851 CFMutableDictionaryRef newdict
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2852 if (newdict
&& dict
&& CFGetTypeID(dict
) == CFDictionaryGetTypeID()) {
2853 CFStringRef srvr
= CFDictionaryGetValue(dict
, kSecAttrServer
);
2854 CFStringRef acct
= CFDictionaryGetValue(dict
, kSecAttrAccount
);
2855 CFNumberRef pnum
= CFDictionaryGetValue(dict
, kSecAttrPort
);
2856 CFStringRef icmt
= CFDictionaryGetValue(dict
, kSecAttrComment
);
2857 CFDataRef data
= CFDictionaryGetValue(dict
, kSecValueData
);
2859 CFDictionaryAddValue(newdict
, kSecAttrServer
, srvr
);
2862 CFDictionaryAddValue(newdict
, kSecAttrAccount
, acct
);
2866 if (CFNumberGetValue(pnum
, kCFNumberSInt16Type
, &pval
) &&
2867 (pval
< -1 || pval
> 0)) {
2868 CFDictionaryAddValue(newdict
, kSecAttrPort
, pnum
);
2872 CFStringRef password
= CFStringCreateFromExternalRepresentation(kCFAllocatorDefault
, data
, kCFStringEncodingUTF8
);
2874 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH && !TARGET_OS_TV
2875 CFDictionaryAddValue(newdict
, kSecSharedPassword
, password
);
2877 CFDictionaryAddValue(newdict
, CFSTR("spwd"), password
);
2879 CFReleaseSafe(password
);
2883 if (acct
&& CFEqual(acct
, kSecSafariPasswordsNotSaved
)) {
2884 // Do not add to credentials list!
2885 secwarning("copySWC: Skipping \"%@\" item", kSecSafariPasswordsNotSaved
);
2886 } else if (icmt
&& CFEqual(icmt
, kSecSafariDefaultComment
)) {
2887 CFArrayInsertValueAtIndex(credentials
, 0, newdict
);
2889 CFArrayAppendValue(credentials
, newdict
);
2892 CFReleaseSafe(newdict
);
2895 count
= CFArrayGetCount(credentials
);
2898 // create a new array of dictionaries (without the actual password) for picker UI
2899 CFMutableArrayRef items
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
2900 for (idx
= 0; idx
< count
; idx
++) {
2901 CFDictionaryRef dict
= (CFDictionaryRef
) CFArrayGetValueAtIndex(credentials
, idx
);
2902 CFMutableDictionaryRef newdict
= CFDictionaryCreateMutableCopy(kCFAllocatorDefault
, 0, dict
);
2903 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH && !TARGET_OS_TV
2904 CFDictionaryRemoveValue(newdict
, kSecSharedPassword
);
2906 CFDictionaryRemoveValue(newdict
, CFSTR("spwd"));
2908 CFArrayAppendValue(items
, newdict
);
2909 CFReleaseSafe(newdict
);
2912 // prompt user to select one of the dictionary items
2913 CFDictionaryRef selected
= swca_copy_selected_dictionary(swca_select_request_id
,
2914 clientAuditToken
, items
, error
);
2916 // find the matching item in our credentials array
2917 CFStringRef srvr
= CFDictionaryGetValue(selected
, kSecAttrServer
);
2918 CFStringRef acct
= CFDictionaryGetValue(selected
, kSecAttrAccount
);
2919 CFNumberRef pnum
= CFDictionaryGetValue(selected
, kSecAttrPort
);
2920 for (idx
= 0; idx
< count
; idx
++) {
2921 CFDictionaryRef dict
= (CFDictionaryRef
) CFArrayGetValueAtIndex(credentials
, idx
);
2922 CFStringRef srvr1
= CFDictionaryGetValue(dict
, kSecAttrServer
);
2923 CFStringRef acct1
= CFDictionaryGetValue(dict
, kSecAttrAccount
);
2924 CFNumberRef pnum1
= CFDictionaryGetValue(dict
, kSecAttrPort
);
2926 if (!srvr
|| !srvr1
|| !CFEqual(srvr
, srvr1
)) continue;
2927 if (!acct
|| !acct1
|| !CFEqual(acct
, acct1
)) continue;
2928 if ((pnum
&& pnum1
) && !CFEqual(pnum
, pnum1
)) continue;
2931 CFReleaseSafe(selected
);
2938 CFReleaseSafe(items
);
2939 CFArrayRemoveAllValues(credentials
);
2940 if (selected
&& ok
) {
2941 #if TARGET_OS_IOS && !TARGET_OS_BRIDGE && !TARGET_OS_SIMULATOR
2942 fqdn
= CFDictionaryGetValue(selected
, kSecAttrServer
);
2944 CFArrayAppendValue(credentials
, selected
);
2948 #if TARGET_OS_IOS && !TARGET_OS_BRIDGE && !TARGET_OS_SIMULATOR
2949 // register confirmation with database
2950 CFRetainSafe(appID
);
2952 if (0 != SWCSetServiceFlags(kSecSharedWebCredentialsService
,
2953 appID
, fqdn
, kSWCFlag_ExternalMask
, kSWCFlag_UserApproved
,
2954 ^void(OSStatus inStatus
, SWCFlags inNewFlags
){
2955 CFReleaseSafe(appID
);
2956 CFReleaseSafe(fqdn
);
2959 // we didn't queue the block
2960 CFReleaseSafe(appID
);
2961 CFReleaseSafe(fqdn
);
2965 CFReleaseSafe(selected
);
2967 else if (NULL
== *error
) {
2968 // found no items, and we haven't already filled in the error
2969 SecError(errSecItemNotFound
, error
, CFSTR("no matching items found"));
2974 CFArrayRemoveAllValues(credentials
);
2975 CFReleaseNull(credentials
);
2977 CFReleaseSafe(foundItems
);
2978 *result
= credentials
;
2979 CFReleaseSafe(swcclient
.accessGroups
);
2980 CFReleaseSafe(fqdns
);
2985 #endif /* SHAREDWEBCREDENTIALS */
2989 // MARK: Keychain backup
2991 CF_RETURNS_RETAINED CFDataRef
2992 _SecServerKeychainCreateBackup(SecurityClient
*client
, CFDataRef keybag
, CFDataRef passcode
, bool emcs
, CFErrorRef
*error
) {
2993 __block CFDataRef backup
;
2994 kc_with_dbt(false, error
, ^bool (SecDbConnectionRef dbt
) {
2996 LKABackupReportStart(!!keybag
, !!passcode
, emcs
);
2998 return kc_transaction_type(dbt
, kSecDbNormalTransactionType
, error
, ^bool{
2999 secnotice("SecServerKeychainCreateBackup", "Performing backup from %s keybag%s", keybag
? "provided" : "device", emcs
? ", EMCS mode" : "");
3001 if (keybag
== NULL
&& passcode
== NULL
) {
3003 backup
= SecServerExportBackupableKeychain(dbt
, client
, KEYBAG_DEVICE
, backup_keybag_handle
, error
);
3004 #else /* !USE_KEYSTORE */
3006 SecError(errSecParam
, error
, CFSTR("Why are you doing this?"));
3008 #endif /* USE_KEYSTORE */
3010 backup
= SecServerKeychainCreateBackup(dbt
, client
, keybag
, passcode
, emcs
, error
);
3012 return (backup
!= NULL
);
3016 secnotice("SecServerKeychainCreateBackup", "Backup result: %s (%@)", backup
? "success" : "fail", error
? *error
: NULL
);
3017 LKABackupReportEnd(!!backup
, error
? *error
: NULL
);
3023 _SecServerKeychainRestore(CFDataRef backup
, SecurityClient
*client
, CFDataRef keybag
, CFDataRef passcode
, CFErrorRef
*error
) {
3024 if (backup
== NULL
|| keybag
== NULL
)
3025 return SecError(errSecParam
, error
, CFSTR("backup or keybag missing"));
3027 __block
bool ok
= true;
3028 ok
&= kc_with_dbt(true, error
, ^bool (SecDbConnectionRef dbconn
) {
3029 return SecServerKeychainRestore(dbconn
, client
, backup
, keybag
, passcode
, error
);
3033 SecKeychainChanged();
3040 _SecServerBackupCopyUUID(CFDataRef data
, CFErrorRef
*error
)
3042 CFStringRef uuid
= NULL
;
3043 CFDictionaryRef backup
;
3045 backup
= CFPropertyListCreateWithData(kCFAllocatorDefault
, data
,
3046 kCFPropertyListImmutable
, NULL
,
3048 if (isDictionary(backup
)) {
3049 uuid
= SecServerBackupGetKeybagUUID(backup
, error
);
3053 CFReleaseNull(backup
);
3061 // MARK: SecItemDataSource
3063 #if SECUREOBJECTSYNC
3065 // Make sure to call this before any writes to the keychain, so that we fire
3066 // up the engines to monitor manifest changes.
3067 SOSDataSourceFactoryRef
SecItemDataSourceFactoryGetDefault(void) {
3068 return SecItemDataSourceFactoryGetShared(kc_dbhandle(NULL
));
3071 /* AUDIT[securityd]:
3072 args_in (ok) is a caller provided, CFDictionaryRef.
3075 CF_RETURNS_RETAINED CFArrayRef
3076 _SecServerKeychainSyncUpdateMessage(CFDictionaryRef updates
, CFErrorRef
*error
) {
3077 // This never fails, trust us!
3078 return SOSCCHandleUpdateMessage(updates
);
3082 // Truthiness in the cloud backup/restore support.
3085 static CFDictionaryRef
3086 _SecServerCopyTruthInTheCloud(CFDataRef keybag
, CFDataRef password
,
3087 CFDictionaryRef backup
, CFErrorRef
*error
)
3089 SOSManifestRef mold
= NULL
, mnow
= NULL
, mdelete
= NULL
, madd
= NULL
;
3090 __block CFMutableDictionaryRef backup_new
= NULL
;
3091 keybag_handle_t bag_handle
;
3092 if (!ks_open_keybag(keybag
, password
, &bag_handle
, error
))
3095 // We need to have a datasource singleton for protection domain
3096 // kSecAttrAccessibleWhenUnlocked and keep a single shared engine
3097 // instance around which we create in the datasource constructor as well.
3098 SOSDataSourceFactoryRef dsf
= SecItemDataSourceFactoryGetDefault();
3099 SOSDataSourceRef ds
= SOSDataSourceFactoryCreateDataSource(dsf
, kSecAttrAccessibleWhenUnlocked
, error
);
3101 backup_new
= backup
? CFDictionaryCreateMutableCopy(kCFAllocatorDefault
, 0, backup
) : CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
3102 mold
= SOSCreateManifestWithBackup(backup
, error
);
3103 SOSEngineRef engine
= SOSDataSourceGetSharedEngine(ds
, error
);
3104 mnow
= SOSEngineCopyManifest(engine
, NULL
);
3106 mnow
= SOSDataSourceCopyManifestWithViewNameSet(ds
, SOSViewsGetV0ViewSet(), error
);
3109 CFReleaseNull(backup_new
);
3110 secerror("failed to obtain manifest for keychain: %@", error
? *error
: NULL
);
3112 SOSManifestDiff(mold
, mnow
, &mdelete
, &madd
, error
);
3115 // Delete everything from the new_backup that is no longer in the datasource according to the datasources manifest.
3116 SOSManifestForEach(mdelete
, ^(CFDataRef digest_data
, bool *stop
) {
3117 CFStringRef deleted_item_key
= CFDataCopyHexString(digest_data
);
3118 CFDictionaryRemoveValue(backup_new
, deleted_item_key
);
3119 CFRelease(deleted_item_key
);
3122 CFMutableArrayRef changes
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
3123 SOSDataSourceForEachObject(ds
, NULL
, madd
, error
, ^void(CFDataRef digest
, SOSObjectRef object
, bool *stop
) {
3124 CFErrorRef localError
= NULL
;
3125 CFDataRef digest_data
= NULL
;
3126 CFTypeRef value
= NULL
;
3128 // Key in our manifest can't be found in db, remove it from our manifest
3129 SOSChangesAppendDelete(changes
, digest
);
3130 } else if (!(digest_data
= SOSObjectCopyDigest(ds
, object
, &localError
))
3131 || !(value
= SOSObjectCopyBackup(ds
, object
, bag_handle
, &localError
))) {
3132 if (SecErrorGetOSStatus(localError
) == errSecDecode
) {
3133 // Ignore decode errors, pretend the objects aren't there
3134 CFRelease(localError
);
3135 // Object undecodable, remove it from our manifest
3136 SOSChangesAppendDelete(changes
, digest
);
3138 // Stop iterating and propagate out all other errors.
3140 *error
= localError
;
3141 CFReleaseNull(backup_new
);
3144 // TODO: Should we skip tombstones here?
3145 CFStringRef key
= CFDataCopyHexString(digest_data
);
3146 CFDictionarySetValue(backup_new
, key
, value
);
3149 CFReleaseSafe(digest_data
);
3150 CFReleaseSafe(value
);
3151 }) || CFReleaseNull(backup_new
);
3153 if (CFArrayGetCount(changes
)) {
3154 if (!SOSEngineUpdateChanges(engine
, kSOSDataSourceSOSTransaction
, changes
, error
)) {
3155 CFReleaseNull(backup_new
);
3158 CFReleaseSafe(changes
);
3160 SOSDataSourceRelease(ds
, error
) || CFReleaseNull(backup_new
);
3163 CFReleaseSafe(mold
);
3164 CFReleaseSafe(mnow
);
3165 CFReleaseSafe(madd
);
3166 CFReleaseSafe(mdelete
);
3167 ks_close_keybag(bag_handle
, error
) || CFReleaseNull(backup_new
);
3173 _SecServerRestoreTruthInTheCloud(CFDataRef keybag
, CFDataRef password
, CFDictionaryRef backup_in
, CFErrorRef
*error
) {
3174 __block
bool ok
= true;
3175 keybag_handle_t bag_handle
;
3176 if (!ks_open_keybag(keybag
, password
, &bag_handle
, error
))
3179 SOSManifestRef mbackup
= SOSCreateManifestWithBackup(backup_in
, error
);
3181 SOSDataSourceFactoryRef dsf
= SecItemDataSourceFactoryGetDefault();
3182 SOSDataSourceRef ds
= SOSDataSourceFactoryCreateDataSource(dsf
, kSecAttrAccessibleWhenUnlocked
, error
);
3183 ok
&= ds
&& SOSDataSourceWith(ds
, error
, ^(SOSTransactionRef txn
, bool *commit
) {
3184 SOSManifestRef mnow
= SOSDataSourceCopyManifestWithViewNameSet(ds
, SOSViewsGetV0BackupViewSet(), error
);
3185 SOSManifestRef mdelete
= NULL
, madd
= NULL
;
3186 SOSManifestDiff(mnow
, mbackup
, &mdelete
, &madd
, error
);
3188 // Don't delete everything in datasource not in backup.
3190 // Add items from the backup
3191 SOSManifestForEach(madd
, ^void(CFDataRef e
, bool *stop
) {
3192 CFDictionaryRef item
= NULL
;
3193 CFStringRef sha1
= CFDataCopyHexString(e
);
3195 item
= CFDictionaryGetValue(backup_in
, sha1
);
3199 CFErrorRef localError
= NULL
;
3201 if (!SOSObjectRestoreObject(ds
, txn
, bag_handle
, item
, &localError
)) {
3202 OSStatus status
= SecErrorGetOSStatus(localError
);
3203 if (status
== errSecDuplicateItem
) {
3204 // Log and ignore duplicate item errors during restore
3205 secnotice("titc", "restore %@ not replacing existing item", item
);
3206 } else if (status
== errSecDecode
) {
3207 // Log and ignore corrupted item errors during restore
3208 secnotice("titc", "restore %@ skipping corrupted item %@", item
, localError
);
3210 if (status
== errSecInteractionNotAllowed
)
3212 // Propagate the first other error upwards (causing the restore to fail).
3213 secerror("restore %@ failed %@", item
, localError
);
3215 if (error
&& !*error
) {
3216 *error
= localError
;
3220 CFReleaseSafe(localError
);
3224 ok
&= SOSDataSourceRelease(ds
, error
);
3225 CFReleaseNull(mdelete
);
3226 CFReleaseNull(madd
);
3227 CFReleaseNull(mnow
);
3232 ok
&= ks_close_keybag(bag_handle
, error
);
3238 CF_RETURNS_RETAINED CFDictionaryRef
3239 _SecServerBackupSyncable(CFDictionaryRef backup
, CFDataRef keybag
, CFDataRef password
, CFErrorRef
*error
) {
3240 require_action_quiet(isData(keybag
), errOut
, SecError(errSecParam
, error
, CFSTR("keybag %@ not a data"), keybag
));
3241 require_action_quiet(!backup
|| isDictionary(backup
), errOut
, SecError(errSecParam
, error
, CFSTR("backup %@ not a dictionary"), backup
));
3242 require_action_quiet(!password
|| isData(password
), errOut
, SecError(errSecParam
, error
, CFSTR("password %@ not a data"), password
));
3244 return _SecServerCopyTruthInTheCloud(keybag
, password
, backup
, error
);
3251 _SecServerRestoreSyncable(CFDictionaryRef backup
, CFDataRef keybag
, CFDataRef password
, CFErrorRef
*error
) {
3253 require_action_quiet(isData(keybag
), errOut
, ok
= SecError(errSecParam
, error
, CFSTR("keybag %@ not a data"), keybag
));
3254 require_action_quiet(isDictionary(backup
), errOut
, ok
= SecError(errSecParam
, error
, CFSTR("backup %@ not a dictionary"), backup
));
3257 require_action_quiet(isData(password
), errOut
, ok
= SecError(errSecParam
, error
, CFSTR("password not a data")));
3260 ok
= _SecServerRestoreTruthInTheCloud(keybag
, password
, backup
, error
);
3265 #endif /* SECUREOBJECTSYNC */
3267 bool _SecServerRollKeysGlue(bool force
, CFErrorRef
*error
) {
3268 return _SecServerRollKeys(force
, NULL
, error
);
3272 bool _SecServerRollKeys(bool force
, SecurityClient
*client
, CFErrorRef
*error
) {
3274 uint32_t keystore_generation_status
= 0;
3275 if (aks_generation(KEYBAG_DEVICE
, generation_noop
, &keystore_generation_status
))
3277 uint32_t current_generation
= keystore_generation_status
& generation_current
;
3279 return kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
3280 bool up_to_date
= s3dl_dbt_keys_current(dbt
, current_generation
, NULL
);
3282 if (force
&& !up_to_date
) {
3283 up_to_date
= s3dl_dbt_update_keys(dbt
, client
, error
);
3285 secerror("Completed roll keys.");
3286 up_to_date
= s3dl_dbt_keys_current(dbt
, current_generation
, NULL
);
3289 secerror("Failed to roll keys.");
3299 InitialSyncItems(CFMutableArrayRef items
, bool limitToCurrent
, CFStringRef agrp
, CFStringRef svce
, const SecDbClass
*qclass
, CFErrorRef
*error
)
3301 bool result
= false;
3304 q
= query_create(qclass
, NULL
, NULL
, error
);
3307 q
->q_return_type
= kSecReturnDataMask
| kSecReturnAttributesMask
;
3308 q
->q_limit
= kSecMatchUnlimited
;
3309 q
->q_keybag
= KEYBAG_DEVICE
;
3311 query_add_attribute(kSecAttrAccessGroup
, agrp
, q
);
3312 query_add_attribute(kSecAttrSynchronizable
, kCFBooleanTrue
, q
);
3313 query_add_attribute(kSecAttrTombstone
, kCFBooleanFalse
, q
);
3315 query_add_attribute(kSecAttrService
, svce
, q
);
3317 result
= kc_with_dbt(false, error
, ^(SecDbConnectionRef dbt
) {
3318 return kc_transaction(dbt
, error
, ^{
3319 CFErrorRef error2
= NULL
;
3321 SecDbItemSelect(q
, dbt
, &error2
, NULL
, ^bool(const SecDbAttr
*attr
) {
3322 return CFDictionaryGetValue(q
->q_item
, attr
->name
);
3323 }, NULL
, NULL
, ^(SecDbItemRef item
, bool *stop
) {
3324 CFErrorRef error3
= NULL
;
3325 secinfo("InitialSyncItems", "Copy item");
3327 CFMutableDictionaryRef attrs
= SecDbItemCopyPListWithMask(item
, kSecDbSyncFlag
, &error3
);
3330 CFStringRef itemvwht
= CFDictionaryGetValue(attrs
, kSecAttrSyncViewHint
);
3332 * Saying its a SOS viewhint is really not the right answer post Triangle
3334 if (isString(itemvwht
) && !SOSViewInSOSSystem(itemvwht
)) {
3338 * Here we encode how PCS stores identities so that we only copy the
3339 * current identites for performance reasons.
3341 if (limitToCurrent
) {
3342 enum { PCS_CURRENT_IDENTITY_OFFSET
= 0x10000 };
3345 CFNumberRef type
= CFDictionaryGetValue(attrs
, kSecAttrType
);
3346 if (!isNumber(type
)) {
3347 // still allow this case since its not a service identity ??
3348 } else if (!CFNumberGetValue(type
, kCFNumberSInt32Type
, &s32
)) {
3350 } else if ((s32
& PCS_CURRENT_IDENTITY_OFFSET
) == 0) {
3355 CFDictionaryAddValue(attrs
, kSecClass
, SecDbItemGetClass(item
)->name
);
3356 CFArrayAppendValue(items
, attrs
);
3359 CFReleaseNull(attrs
);
3361 CFReleaseNull(error3
);
3363 CFReleaseNull(error2
);
3371 query_destroy(q
, NULL
);
3376 _SecServerCopyInitialSyncCredentials(uint32_t flags
, CFErrorRef
*error
)
3378 CFMutableArrayRef items
= CFArrayCreateMutableForCFTypes(NULL
);
3380 if (flags
& SecServerInitialSyncCredentialFlagTLK
) {
3381 require_action(InitialSyncItems(items
, false, CFSTR("com.apple.security.ckks"), NULL
, inet_class(), error
), fail
,
3382 secerror("failed to collect CKKS-inet keys: %@", error
? *error
: NULL
));
3384 if (flags
& SecServerInitialSyncCredentialFlagPCS
) {
3385 bool onlyCurrent
= !(flags
& SecServerInitialSyncCredentialFlagPCSNonCurrent
);
3387 require_action(InitialSyncItems(items
, false, CFSTR("com.apple.ProtectedCloudStorage"), NULL
, genp_class(), error
), fail
,
3388 secerror("failed to collect PCS-genp keys: %@", error
? *error
: NULL
));
3389 require_action(InitialSyncItems(items
, onlyCurrent
, CFSTR("com.apple.ProtectedCloudStorage"), NULL
, inet_class(), error
), fail
,
3390 secerror("failed to collect PCS-inet keys: %@", error
? *error
: NULL
));
3392 if (flags
& SecServerInitialSyncCredentialFlagBluetoothMigration
) {
3393 require_action(InitialSyncItems(items
, false, CFSTR("com.apple.nanoregistry.migration"), NULL
, genp_class(), error
), fail
,
3394 secerror("failed to collect com.apple.nanoregistry.migration-genp item: %@", error
? *error
: NULL
));
3395 require_action(InitialSyncItems(items
, false, CFSTR("com.apple.nanoregistry.migration2"), NULL
, genp_class(), error
), fail
,
3396 secerror("failed to collect com.apple.nanoregistry.migration2-genp item: %@", error
? *error
: NULL
));
3397 require_action(InitialSyncItems(items
, false, CFSTR("com.apple.bluetooth"), CFSTR("BluetoothLESync"), genp_class(), error
), fail
,
3398 secerror("failed to collect com.apple.bluetooth-genp item: %@", error
? *error
: NULL
));
3407 _SecServerImportInitialSyncCredentials(CFArrayRef array
, CFErrorRef
*error
)
3409 return kc_with_dbt(true, error
, ^bool(SecDbConnectionRef dbt
) {
3410 return kc_transaction(dbt
, error
, ^bool(void){
3411 CFIndex n
, count
= CFArrayGetCount(array
);
3413 secinfo("ImportInitialSyncItems", "Importing %d items", (int)count
);
3415 for (n
= 0; n
< count
; n
++) {
3416 CFErrorRef cferror
= NULL
;
3418 CFDictionaryRef item
= CFArrayGetValueAtIndex(array
, n
);
3419 if (!isDictionary(item
))
3422 CFStringRef className
= CFDictionaryGetValue(item
, kSecClass
);
3423 if (className
== NULL
) {
3424 secinfo("ImportInitialSyncItems", "Item w/o class");
3428 const SecDbClass
*cls
= kc_class_with_name(className
);
3430 secinfo("ImportInitialSyncItems", "Item with unknown class: %@", className
);
3434 SecDbItemRef dbi
= SecDbItemCreateWithAttributes(NULL
, cls
, item
, KEYBAG_DEVICE
, &cferror
);
3436 secinfo("ImportInitialSyncItems", "Item creation failed with: %@", cferror
);
3437 CFReleaseNull(cferror
);
3441 if (!SecDbItemSetSyncable(dbi
, true, &cferror
)) {
3442 secinfo("ImportInitialSyncItems", "Failed to set sync=1: %@ for item %@", cferror
, dbi
);
3443 CFReleaseNull(cferror
);
3448 if (!SecDbItemInsert(dbi
, dbt
, &cferror
)) {
3449 secinfo("ImportInitialSyncItems", "Item store failed with: %@: %@", cferror
, dbi
);
3450 CFReleaseNull(cferror
);
3464 * Sync bubble migration code
3467 struct SyncBubbleRule
{
3468 CFStringRef attribute
;
3473 TransmogrifyItemsToSyncBubble(SecurityClient
*client
, uid_t uid
,
3476 const SecDbClass
*qclass
,
3477 struct SyncBubbleRule
*items
, CFIndex nItems
,
3480 CFMutableDictionaryRef updateAttributes
= NULL
;
3481 CFDataRef syncBubbleView
= NULL
;
3482 CFDataRef activeUserView
= NULL
;
3487 syncBubbleView
= SecMUSRCreateSyncBubbleUserUUID(uid
);
3488 require(syncBubbleView
, fail
);
3490 activeUserView
= SecMUSRCreateActiveUserUUID(uid
);
3491 require(activeUserView
, fail
);
3494 if ((onlyDelete
&& !copyToo
) || !onlyDelete
) {
3497 * Clean out items first
3500 secnotice("syncbubble", "cleaning out old items");
3502 q
= query_create(qclass
, NULL
, NULL
, error
);
3505 q
->q_limit
= kSecMatchUnlimited
;
3506 q
->q_keybag
= device_keybag_handle
;
3508 for (n
= 0; n
< nItems
; n
++) {
3509 query_add_attribute(items
[n
].attribute
, items
[n
].value
, q
);
3511 q
->q_musrView
= CFRetain(syncBubbleView
);
3512 require(q
->q_musrView
, fail
);
3514 kc_with_dbt(false, error
, ^(SecDbConnectionRef dbt
) {
3515 return kc_transaction(dbt
, error
, ^{
3516 return s3dl_query_delete(dbt
, q
, NULL
, error
);
3520 query_destroy(q
, NULL
);
3525 if (onlyDelete
|| !copyToo
) {
3526 secnotice("syncbubble", "skip migration of items");
3529 * Copy over items from EMCS to sync bubble
3532 secnotice("syncbubble", "migrating sync bubble items");
3534 q
= query_create(qclass
, NULL
, NULL
, error
);
3537 q
->q_return_type
= kSecReturnDataMask
| kSecReturnAttributesMask
;
3538 q
->q_limit
= kSecMatchUnlimited
;
3539 q
->q_keybag
= device_keybag_handle
; /* XXX change to session key bag when it exists */
3541 for (n
= 0; n
< nItems
; n
++) {
3542 query_add_or_attribute(items
[n
].attribute
, items
[n
].value
, q
);
3544 query_add_or_attribute(CFSTR("musr"), activeUserView
, q
);
3545 q
->q_musrView
= CFRetain(activeUserView
);
3547 updateAttributes
= CFDictionaryCreateMutableForCFTypes(NULL
);
3548 require(updateAttributes
, fail
);
3550 CFDictionarySetValue(updateAttributes
, CFSTR("musr"), syncBubbleView
); /* XXX should use kSecAttrMultiUser */
3553 kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
3554 return kc_transaction(dbt
, error
, ^{
3555 CFErrorRef error2
= NULL
;
3557 SecDbItemSelect(q
, dbt
, &error2
, NULL
, ^bool(const SecDbAttr
*attr
) {
3558 return CFDictionaryGetValue(q
->q_item
, attr
->name
);
3559 }, NULL
, NULL
, ^(SecDbItemRef item
, bool *stop
) {
3560 CFErrorRef error3
= NULL
;
3561 secinfo("syncbubble", "migrating item");
3563 SecDbItemRef new_item
= SecDbItemCopyWithUpdates(item
, updateAttributes
, NULL
);
3564 if (new_item
== NULL
)
3567 SecDbItemClearRowId(new_item
, NULL
);
3569 if (!SecDbItemSetKeybag(new_item
, device_keybag_handle
, NULL
)) {
3570 CFRelease(new_item
);
3574 if (!SecDbItemInsert(new_item
, dbt
, &error3
)) {
3575 secnotice("syncbubble", "migration failed with %@ for item %@", error3
, new_item
);
3577 CFRelease(new_item
);
3578 CFReleaseNull(error3
);
3580 CFReleaseNull(error2
);
3589 CFReleaseNull(syncBubbleView
);
3590 CFReleaseNull(activeUserView
);
3591 CFReleaseNull(updateAttributes
);
3593 query_destroy(q
, NULL
);
3598 static struct SyncBubbleRule PCSItems
[] = {
3600 .attribute
= CFSTR("agrp"),
3601 .value
= CFSTR("com.apple.ProtectedCloudStorage"),
3604 static struct SyncBubbleRule NSURLSesssiond
[] = {
3606 .attribute
= CFSTR("agrp"),
3607 .value
= CFSTR("com.apple.nsurlsessiond"),
3610 static struct SyncBubbleRule AccountsdItems
[] = {
3612 .attribute
= CFSTR("svce"),
3613 .value
= CFSTR("com.apple.account.AppleAccount.token"),
3616 .attribute
= CFSTR("svce"),
3617 .value
= CFSTR("com.apple.account.AppleAccount.password"),
3620 .attribute
= CFSTR("svce"),
3621 .value
= CFSTR("com.apple.account.AppleAccount.rpassword"),
3624 .attribute
= CFSTR("svce"),
3625 .value
= CFSTR("com.apple.account.idms.token"),
3628 .attribute
= CFSTR("svce"),
3629 .value
= CFSTR("com.apple.account.idms.continuation-key"),
3632 .attribute
= CFSTR("svce"),
3633 .value
= CFSTR("com.apple.account.CloudKit.token"),
3637 static struct SyncBubbleRule MobileMailItems
[] = {
3639 .attribute
= CFSTR("svce"),
3640 .value
= CFSTR("com.apple.account.IMAP.password"),
3643 .attribute
= CFSTR("svce"),
3644 .value
= CFSTR("com.apple.account.SMTP.password"),
3647 .attribute
= CFSTR("svce"),
3648 .value
= CFSTR("com.apple.account.Exchange.password"),
3651 .attribute
= CFSTR("svce"),
3652 .value
= CFSTR("com.apple.account.Hotmail.password"),
3655 .attribute
= CFSTR("svce"),
3656 .value
= CFSTR("com.apple.account.Google.password"),
3659 .attribute
= CFSTR("svce"),
3660 .value
= CFSTR("com.apple.account.Google.oauth-token"),
3663 .attribute
= CFSTR("svce"),
3664 .value
= CFSTR("com.apple.account.Google.oath-refresh-token"),
3667 .attribute
= CFSTR("svce"),
3668 .value
= CFSTR("com.apple.account.Yahoo.password"),
3671 .attribute
= CFSTR("svce"),
3672 .value
= CFSTR("com.apple.account.Yahoo.oauth-token"),
3675 .attribute
= CFSTR("svce"),
3676 .value
= CFSTR("com.apple.account.Yahoo.oauth-token-nosync"),
3679 .attribute
= CFSTR("svce"),
3680 .value
= CFSTR("com.apple.account.Yahoo.oath-refresh-token"),
3683 .attribute
= CFSTR("svce"),
3684 .value
= CFSTR("com.apple.account.IMAPNotes.password"),
3687 .attribute
= CFSTR("svce"),
3688 .value
= CFSTR("com.apple.account.IMAPMail.password"),
3691 .attribute
= CFSTR("svce"),
3692 .value
= CFSTR("com.apple.account.126.password"),
3695 .attribute
= CFSTR("svce"),
3696 .value
= CFSTR("com.apple.account.163.password"),
3699 .attribute
= CFSTR("svce"),
3700 .value
= CFSTR("com.apple.account.aol.password"),
3705 ArrayContains(CFArrayRef array
, CFStringRef service
)
3707 return CFArrayContainsValue(array
, CFRangeMake(0, CFArrayGetCount(array
)), service
);
3711 _SecServerTransmogrifyToSyncBubble(CFArrayRef services
, uid_t uid
, SecurityClient
*client
, CFErrorRef
*error
)
3713 bool copyCloudAuthToken
= false;
3714 bool copyMobileMail
= false;
3716 bool copyPCS
= false;
3717 bool onlyDelete
= false;
3718 bool copyNSURLSesssion
= false;
3720 if (!client
->inMultiUser
)
3723 secnotice("syncbubble", "migration for uid %d uid for services %@", (int)uid
, services
);
3725 #if TARGET_OS_SIMULATOR
3728 if (uid
!= (uid_t
)client
->activeUser
)
3731 #error "no sync bubble on other platforms"
3735 * First select that services to copy/delete
3738 if (ArrayContains(services
, CFSTR("com.apple.bird.usermanager.sync"))
3739 || ArrayContains(services
, CFSTR("com.apple.cloudphotod.sync"))
3740 || ArrayContains(services
, CFSTR("com.apple.cloudphotod.syncstakeholder"))
3741 || ArrayContains(services
, CFSTR("com.apple.cloudd.usermanager.sync")))
3743 copyCloudAuthToken
= true;
3747 if (ArrayContains(services
, CFSTR("com.apple.nsurlsessiond.usermanager.sync")))
3749 copyCloudAuthToken
= true;
3750 copyNSURLSesssion
= true;
3753 if (ArrayContains(services
, CFSTR("com.apple.syncdefaultsd.usermanager.sync"))) {
3754 copyCloudAuthToken
= true;
3756 if (ArrayContains(services
, CFSTR("com.apple.mailq.sync")) || ArrayContains(services
, CFSTR("com.apple.mailq.sync.xpc"))) {
3757 copyCloudAuthToken
= true;
3758 copyMobileMail
= true;
3763 * The actually copy/delete the items selected
3766 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyPCS
, inet_class(), PCSItems
, sizeof(PCSItems
)/sizeof(PCSItems
[0]), error
);
3768 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyPCS
, genp_class(), PCSItems
, sizeof(PCSItems
)/sizeof(PCSItems
[0]), error
);
3772 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyMobileMail
, genp_class(), MobileMailItems
, sizeof(MobileMailItems
)/sizeof(MobileMailItems
[0]), error
);
3776 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyCloudAuthToken
, genp_class(), AccountsdItems
, sizeof(AccountsdItems
)/sizeof(AccountsdItems
[0]), error
);
3780 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyNSURLSesssion
, inet_class(), NSURLSesssiond
, sizeof(NSURLSesssiond
)/sizeof(NSURLSesssiond
[0]), error
);
3788 * Migrate from user keychain to system keychain when switching to edu mode
3792 _SecServerTransmogrifyToSystemKeychain(SecurityClient
*client
, CFErrorRef
*error
)
3794 __block
bool ok
= true;
3797 * we are not in multi user yet, about to switch, otherwise we would
3798 * check that for client->inMultiuser here
3801 kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
3802 return kc_transaction(dbt
, error
, ^{
3803 CFDataRef systemUUID
= SecMUSRGetSystemKeychainUUID();
3805 const SecDbSchema
*newSchema
= current_schema();
3806 SecDbClass
const *const *kcClass
;
3808 for (kcClass
= newSchema
->classes
; *kcClass
!= NULL
; kcClass
++) {
3809 CFErrorRef localError
= NULL
;
3812 if (!((*kcClass
)->itemclass
)) {
3816 q
= query_create(*kcClass
, SecMUSRGetSingleUserKeychainUUID(), NULL
, error
);
3820 ok
&= SecDbItemSelect(q
, dbt
, error
, ^bool(const SecDbAttr
*attr
) {
3821 return (attr
->flags
& kSecDbInFlag
) != 0;
3822 }, ^bool(const SecDbAttr
*attr
) {
3823 // No filtering please.
3825 }, ^bool(CFMutableStringRef sql
, bool *needWhere
) {
3826 SecDbAppendWhereOrAnd(sql
, needWhere
);
3827 CFStringAppendFormat(sql
, NULL
, CFSTR("musr = ?"));
3829 }, ^bool(sqlite3_stmt
*stmt
, int col
) {
3830 return SecDbBindObject(stmt
, col
++, SecMUSRGetSingleUserKeychainUUID(), error
);
3831 }, ^(SecDbItemRef item
, bool *stop
) {
3832 CFErrorRef itemError
= NULL
;
3834 if (!SecDbItemSetValueWithName(item
, kSecAttrMultiUser
, systemUUID
, &itemError
)) {
3835 secerror("item: %@ update musr to system failed: %@", item
, itemError
);
3840 if (!SecDbItemDoUpdate(item
, item
, dbt
, &itemError
, ^bool (const SecDbAttr
*attr
) {
3841 return attr
->kind
== kSecDbRowIdAttr
;
3843 secerror("item: %@ insert during UPDATE: %@", item
, itemError
);
3849 SecErrorPropagate(itemError
, error
);
3853 query_destroy(q
, &localError
);
3864 * Delete account from local usage
3868 _SecServerDeleteMUSERViews(SecurityClient
*client
, uid_t uid
, CFErrorRef
*error
)
3870 return kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
3871 CFDataRef musrView
= NULL
, syncBubbleView
= NULL
;
3874 syncBubbleView
= SecMUSRCreateSyncBubbleUserUUID(uid
);
3875 require(syncBubbleView
, fail
);
3877 musrView
= SecMUSRCreateActiveUserUUID(uid
);
3878 require(musrView
, fail
);
3880 require(ok
= SecServerDeleteAllForUser(dbt
, syncBubbleView
, false, error
), fail
);
3881 require(ok
= SecServerDeleteAllForUser(dbt
, musrView
, false, error
), fail
);
3884 CFReleaseNull(syncBubbleView
);
3885 CFReleaseNull(musrView
);
3891 #endif /* TARGET_OS_IOS */
3893 CFArrayRef
_SecItemCopyParentCertificates(CFDataRef normalizedIssuer
, CFArrayRef accessGroups
, CFErrorRef
*error
) {
3894 const void *keys
[] = {
3901 kSecClassCertificate
,
3906 CFDictionaryRef query
= CFDictionaryCreate(NULL
, keys
, values
, 4,
3908 CFTypeRef results
= NULL
;
3909 SecurityClient client
= {
3911 .accessGroups
= accessGroups
,
3912 .allowSystemKeychain
= true,
3913 .allowSyncBubbleKeychain
= false,
3914 .isNetworkExtension
= false,
3917 (void)_SecItemCopyMatching(query
, &client
, &results
, error
);
3922 bool _SecItemCertificateExists(CFDataRef normalizedIssuer
, CFDataRef serialNumber
, CFArrayRef accessGroups
, CFErrorRef
*error
) {
3923 const void *keys
[] = {
3927 kSecAttrSerialNumber
3930 kSecClassCertificate
,
3935 SecurityClient client
= {
3937 .accessGroups
= accessGroups
,
3938 .allowSystemKeychain
= true,
3939 .allowSyncBubbleKeychain
= false,
3940 .isNetworkExtension
= false,
3942 CFDictionaryRef query
= CFDictionaryCreate(NULL
, keys
, values
, 4, NULL
, NULL
);
3943 CFTypeRef results
= NULL
;
3944 bool ok
= _SecItemCopyMatching(query
, &client
, &results
, error
);
3945 CFReleaseSafe(query
);
3946 CFReleaseSafe(results
);