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 "keychain/securityd/SecItemServer.h"
39 #include "keychain/securityd/SecItemDataSource.h"
40 #include "keychain/securityd/SecItemDb.h"
41 #include "keychain/securityd/SecItemSchema.h"
42 #include <utilities/SecDb.h>
43 #include "keychain/securityd/SecDbKeychainItem.h"
44 #include "keychain/securityd/SOSCloudCircleServer.h"
45 #include <Security/SecBasePriv.h>
46 #include <Security/SecItemPriv.h>
47 #include <Security/SecItemInternal.h>
48 #include "keychain/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 // This needs to be async, otherwise we get hangs between securityd, cloudd, and apsd
1161 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT
, 0), ^{
1162 if(OctagonIsEnabled() && OctagonShouldPerformInitialization()) {
1163 OctagonInitialize();
1166 if(SecCKKSIsEnabled()) {
1167 SecCKKSInitialize(db
);
1171 if(EscrowRequestServerIsEnabled()) {
1172 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT
, 0), ^{
1173 EscrowRequestServerInitialize();
1181 static SecDbRef _kc_dbhandle
= NULL
;
1182 static dispatch_queue_t _kc_dbhandle_dispatch
= NULL
;
1183 static dispatch_once_t _kc_dbhandle_dispatch_onceToken
= 0;
1184 static dispatch_queue_t
get_kc_dbhandle_dispatch() {
1185 dispatch_once(&_kc_dbhandle_dispatch_onceToken
, ^{
1186 _kc_dbhandle_dispatch
= dispatch_queue_create("sec_kc_dbhandle", DISPATCH_QUEUE_SERIAL
);
1189 return _kc_dbhandle_dispatch
;
1192 static bool kc_dbhandle_init(CFErrorRef
* error
) {
1193 SecDbRef oldHandle
= _kc_dbhandle
;
1194 _kc_dbhandle
= NULL
;
1195 CFStringRef dbPath
= __SecKeychainCopyPath();
1197 _kc_dbhandle
= SecKeychainDbCreate(dbPath
, error
);
1200 secerror("no keychain path available");
1203 secerror("replaced %@ with %@", oldHandle
, _kc_dbhandle
);
1204 CFRelease(oldHandle
);
1206 // Having a dbhandle means we succeeded.
1207 return !!_kc_dbhandle
;
1210 static SecDbRef
kc_dbhandle(CFErrorRef
* error
)
1212 dispatch_sync(get_kc_dbhandle_dispatch(), ^{
1213 if(_kc_dbhandle
== NULL
) {
1214 _SecDbServerSetup();
1215 kc_dbhandle_init(error
);
1218 return _kc_dbhandle
;
1221 /* whitebox testing only, and I really hope you call DbReset soon */
1222 void SecKeychainDbForceClose(void)
1224 dispatch_sync(get_kc_dbhandle_dispatch(), ^{
1226 SecDbForceClose(_kc_dbhandle
);
1231 /* For whitebox testing only */
1232 void SecKeychainDbReset(dispatch_block_t inbetween
)
1234 dispatch_sync(get_kc_dbhandle_dispatch(), ^{
1235 CFReleaseNull(_kc_dbhandle
);
1236 SecDbResetMetadataKeys();
1243 static bool kc_acquire_dbt(bool writeAndRead
, SecDbConnectionRef
* dbconn
, CFErrorRef
*error
) {
1244 SecDbRef db
= kc_dbhandle(error
);
1246 if(error
&& !(*error
)) {
1247 SecError(errSecDataNotAvailable
, error
, CFSTR("failed to get a db handle"));
1252 return SecDbConnectionAcquireRefMigrationSafe(db
, !writeAndRead
, dbconn
, error
);
1255 /* Return a per thread dbt handle for the keychain. If create is true create
1256 the database if it does not yet exist. If it is false, just return an
1257 error if it fails to auto-create. */
1258 bool kc_with_dbt(bool writeAndRead
, CFErrorRef
*error
, bool (^perform
)(SecDbConnectionRef dbt
))
1260 return kc_with_custom_db(writeAndRead
, true, NULL
, error
, perform
);
1263 bool kc_with_dbt_non_item_tables(bool writeAndRead
, CFErrorRef
* error
, bool (^perform
)(SecDbConnectionRef dbt
))
1265 return kc_with_custom_db(writeAndRead
, false, NULL
, error
, perform
);
1268 bool kc_with_custom_db(bool writeAndRead
, bool usesItemTables
, SecDbRef db
, CFErrorRef
*error
, bool (^perform
)(SecDbConnectionRef dbt
))
1270 if (db
&& db
!= kc_dbhandle(error
)) {
1271 __block
bool result
= false;
1273 SecDbPerformWrite(db
, error
, ^(SecDbConnectionRef dbconn
) {
1274 result
= perform(dbconn
);
1278 SecDbPerformRead(db
, error
, ^(SecDbConnectionRef dbconn
) {
1279 result
= perform(dbconn
);
1286 // The kc_with_dbt upthread will clean this up when it's done.
1287 return perform(threadDbt
);
1290 if (writeAndRead
&& usesItemTables
) {
1291 #if SECUREOBJECTSYNC
1292 SecItemDataSourceFactoryGetDefault();
1297 if (kc_acquire_dbt(writeAndRead
, &threadDbt
, error
)) {
1298 ok
= perform(threadDbt
);
1299 SecDbConnectionRelease(threadDbt
);
1306 items_matching_issuer_parent(SecDbConnectionRef dbt
, CFArrayRef accessGroups
, CFDataRef musrView
,
1307 CFDataRef issuer
, CFArrayRef issuers
, int recurse
)
1310 CFArrayRef results
= NULL
;
1314 if (CFArrayContainsValue(issuers
, CFRangeMake(0, CFArrayGetCount(issuers
)), issuer
))
1317 /* XXX make musr supported */
1318 const void *keys
[] = { kSecClass
, kSecReturnRef
, kSecAttrSubject
};
1319 const void *vals
[] = { kSecClassCertificate
, kCFBooleanTrue
, issuer
};
1320 CFDictionaryRef query
= CFDictionaryCreate(kCFAllocatorDefault
, keys
, vals
, array_size(keys
), NULL
, NULL
);
1325 CFErrorRef localError
= NULL
;
1326 q
= query_create_with_limit(query
, musrView
, kSecMatchUnlimited
, &localError
);
1329 s3dl_copy_matching(dbt
, q
, (CFTypeRef
*)&results
, accessGroups
, &localError
);
1330 query_destroy(q
, &localError
);
1333 secerror("items matching issuer parent: %@", localError
);
1334 CFReleaseNull(localError
);
1338 count
= CFArrayGetCount(results
);
1339 for (i
= 0; (i
< count
) && !found
; i
++) {
1340 CFDictionaryRef cert_dict
= (CFDictionaryRef
)CFArrayGetValueAtIndex(results
, i
);
1341 CFDataRef cert_issuer
= CFDictionaryGetValue(cert_dict
, kSecAttrIssuer
);
1342 if (CFEqual(cert_issuer
, issuer
))
1345 found
= items_matching_issuer_parent(dbt
, accessGroups
, musrView
, cert_issuer
, issuers
, recurse
);
1347 CFReleaseSafe(results
);
1353 _FilterWithPolicy(SecPolicyRef policy
, CFDateRef date
, SecCertificateRef cert
)
1355 CFDictionaryRef props
= NULL
;
1356 CFArrayRef keychains
= NULL
;
1357 CFArrayRef anchors
= NULL
;
1358 CFArrayRef certs
= NULL
;
1359 CFArrayRef chain
= NULL
;
1360 SecTrustRef trust
= NULL
;
1362 SecTrustResultType trustResult
;
1363 Boolean needChain
= false;
1364 __block
bool ok
= false;
1366 if (!policy
|| !cert
) return false;
1368 certs
= CFArrayCreate(NULL
, (const void **)&cert
, (CFIndex
)1, &kCFTypeArrayCallBacks
);
1369 require_noerr_quiet(SecTrustCreateWithCertificates(certs
, policy
, &trust
), cleanup
);
1371 /* Set evaluation date, if specified (otherwise current date is implied) */
1372 if (date
&& (CFGetTypeID(date
) == CFDateGetTypeID())) {
1373 require_noerr_quiet(SecTrustSetVerifyDate(trust
, date
), cleanup
);
1376 /* Check whether this is the X509 Basic policy, which means chain building */
1377 props
= SecPolicyCopyProperties(policy
);
1379 CFTypeRef oid
= (CFTypeRef
) CFDictionaryGetValue(props
, kSecPolicyOid
);
1380 if (oid
&& (CFEqual(oid
, kSecPolicyAppleX509Basic
) ||
1381 CFEqual(oid
, kSecPolicyAppleRevocation
))) {
1387 require_noerr_quiet(SecTrustEvaluateLeafOnly(trust
, &trustResult
), cleanup
);
1389 require_noerr_quiet(SecTrustEvaluate(trust
, &trustResult
), cleanup
);
1392 require_quiet((trustResult
== kSecTrustResultProceed
||
1393 trustResult
== kSecTrustResultUnspecified
||
1394 trustResult
== kSecTrustResultRecoverableTrustFailure
), cleanup
);
1397 #if TARGET_OS_IPHONE
1398 CFArrayRef properties
= SecTrustCopyProperties(trust
);
1400 CFArrayRef properties
= SecTrustCopyProperties_ios(trust
);
1403 CFArrayForEach(properties
, ^(const void *property
) {
1404 CFDictionaryForEach((CFDictionaryRef
)property
, ^(const void *key
, const void *value
) {
1405 if (CFEqual((CFTypeRef
)key
, kSecPropertyKeyType
) && CFEqual((CFTypeRef
)value
, kSecPropertyTypeError
))
1409 CFRelease(properties
);
1413 if(props
) CFRelease(props
);
1414 if(chain
) CFRelease(chain
);
1415 if(anchors
) CFRelease(anchors
);
1416 if(keychains
) CFRelease(keychains
);
1417 if(certs
) CFRelease(certs
);
1418 if(trust
) CFRelease(trust
);
1424 _FilterWithDate(CFDateRef validOnDate
, SecCertificateRef cert
)
1426 if (!validOnDate
|| !cert
) return false;
1428 CFAbsoluteTime at
, nb
, na
;
1429 at
= CFDateGetAbsoluteTime((CFDateRef
)validOnDate
);
1432 nb
= SecCertificateNotValidBefore(cert
);
1433 na
= SecCertificateNotValidAfter(cert
);
1435 if (nb
== 0 || na
== 0 || nb
== na
) {
1437 secnotice("FilterWithDate", "certificate cannot operate");
1441 secnotice("FilterWithDate", "certificate is not valid yet");
1445 secnotice("FilterWithDate", "certificate expired");
1452 _FilterWithTrust(Boolean trustedOnly
, SecCertificateRef cert
)
1454 if (!cert
) return false;
1455 if (!trustedOnly
) return true;
1458 CFArrayRef certArray
= CFArrayCreate(NULL
, (const void**)&cert
, 1, &kCFTypeArrayCallBacks
);
1459 SecTrustRef trust
= NULL
;
1460 SecPolicyRef policy
= SecPolicyCreateBasicX509();
1461 require_quiet(policy
, out
);
1463 require_noerr_quiet(SecTrustCreateWithCertificates(certArray
, policy
, &trust
), out
);
1464 SecTrustResultType trustResult
;
1465 require_noerr_quiet(SecTrustEvaluate(trust
, &trustResult
), out
);
1467 require_quiet((trustResult
== kSecTrustResultProceed
||
1468 trustResult
== kSecTrustResultUnspecified
), out
);
1471 CFReleaseSafe(trust
);
1472 CFReleaseSafe(policy
);
1473 CFReleaseSafe(certArray
);
1477 static SecCertificateRef
1478 CopyCertificateFromItem(Query
*q
, CFDictionaryRef item
) {
1479 SecCertificateRef certRef
= NULL
;
1480 CFDictionaryRef itemValue
= NULL
;
1482 CFTypeRef tokenID
= NULL
;
1483 CFDataRef certData
= NULL
;
1484 if (q
->q_class
== identity_class()) {
1485 certData
= CFDictionaryGetValue(item
, kSecAttrIdentityCertificateData
);
1486 tokenID
= CFDictionaryGetValue(item
, kSecAttrIdentityCertificateTokenID
);
1487 } else if (q
->q_class
== cert_class()) {
1488 certData
= CFDictionaryGetValue(item
, kSecValueData
);
1489 tokenID
= CFDictionaryGetValue(item
, kSecAttrTokenID
);
1492 require_quiet(certData
, out
);
1493 if (tokenID
!= NULL
) {
1494 CFErrorRef error
= NULL
;
1495 itemValue
= SecTokenItemValueCopy(certData
, &error
);
1496 require_action_quiet(itemValue
, out
, { secerror("function SecTokenItemValueCopy failed with: %@", error
); CFReleaseSafe(error
); });
1497 CFDataRef tokenCertData
= CFDictionaryGetValue(itemValue
, kSecTokenValueDataKey
);
1498 require_action_quiet(tokenCertData
, out
, { secerror("token item doesn't contain token value data");});
1499 certRef
= SecCertificateCreateWithData(kCFAllocatorDefault
, tokenCertData
);
1502 certRef
= SecCertificateCreateWithData(kCFAllocatorDefault
, certData
);
1505 CFReleaseNull(itemValue
);
1509 bool match_item(SecDbConnectionRef dbt
, Query
*q
, CFArrayRef accessGroups
, CFDictionaryRef item
)
1512 SecCertificateRef certRef
= NULL
;
1513 if (q
->q_match_issuer
) {
1514 CFDataRef issuer
= CFDictionaryGetValue(item
, kSecAttrIssuer
);
1515 if (!items_matching_issuer_parent(dbt
, accessGroups
, q
->q_musrView
, issuer
, q
->q_match_issuer
, 10 /*max depth*/))
1519 if (q
->q_match_policy
&& (q
->q_class
== identity_class() || q
->q_class
== cert_class())) {
1521 certRef
= CopyCertificateFromItem(q
, item
);
1522 require_quiet(certRef
, out
);
1523 require_quiet(_FilterWithPolicy(q
->q_match_policy
, q
->q_match_valid_on_date
, certRef
), out
);
1526 if (q
->q_match_valid_on_date
&& (q
->q_class
== identity_class() || q
->q_class
== cert_class())) {
1528 certRef
= CopyCertificateFromItem(q
, item
);
1529 require_quiet(certRef
, out
);
1530 require_quiet(_FilterWithDate(q
->q_match_valid_on_date
, certRef
), out
);
1533 if (q
->q_match_trusted_only
&& (q
->q_class
== identity_class() || q
->q_class
== cert_class())) {
1535 certRef
= CopyCertificateFromItem(q
, item
);
1536 require_quiet(certRef
, out
);
1537 require_quiet(_FilterWithTrust(CFBooleanGetValue(q
->q_match_trusted_only
), certRef
), out
);
1540 /* Add future match checks here. */
1543 CFReleaseSafe(certRef
);
1547 /****************************************************************************
1548 **************** Beginning of Externally Callable Interface ****************
1549 ****************************************************************************/
1551 static bool SecEntitlementError(CFErrorRef
*error
)
1554 #define SEC_ENTITLEMENT_WARNING CFSTR("com.apple.application-identifier nor com.apple.security.application-groups nor keychain-access-groups")
1555 #elif TARGET_OS_IOSMAC
1556 #define SEC_ENTITLEMENT_WARNING CFSTR("com.apple.developer.associated-application-identifier nor application-identifier nor com.apple.security.application-groups nor keychain-access-groups")
1558 #define SEC_ENTITLEMENT_WARNING CFSTR("application-identifier nor keychain-access-groups")
1561 return SecError(errSecMissingEntitlement
, error
, CFSTR("Client has neither %@ entitlements"), SEC_ENTITLEMENT_WARNING
);
1564 static bool SecEntitlementErrorForExplicitAccessGroup(CFStringRef agrp
, CFArrayRef clientGroups
, CFErrorRef
* error
)
1566 return SecError(errSecMissingEntitlement
, error
, CFSTR("Client explicitly specifies access group %@ but is only entitled for %@"), agrp
, clientGroups
);
1569 static CFStringRef
CopyAccessGroupForRowID(sqlite_int64 rowID
, CFStringRef itemClass
)
1571 __block CFStringRef accessGroup
= NULL
;
1573 __block CFErrorRef error
= NULL
;
1574 bool ok
= kc_with_dbt(false, &error
, ^bool(SecDbConnectionRef dbt
) {
1575 CFStringRef table
= CFEqual(itemClass
, kSecClassIdentity
) ? kSecClassCertificate
: itemClass
;
1576 CFStringRef sql
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("SELECT agrp FROM %@ WHERE rowid == %u"), table
, (unsigned int)rowID
);
1577 bool dbOk
= SecDbWithSQL(dbt
, sql
, &error
, ^bool(sqlite3_stmt
*stmt
) {
1578 bool rowOk
= SecDbForEach(dbt
, stmt
, &error
, ^bool(int row_index
) {
1579 accessGroup
= CFStringCreateWithBytes(NULL
, sqlite3_column_blob(stmt
, 0), sqlite3_column_bytes(stmt
, 0), kCFStringEncodingUTF8
, false);
1580 return accessGroup
!= NULL
;
1583 return (bool)(rowOk
&& accessGroup
!= NULL
);
1587 return (bool)(dbOk
&& accessGroup
);
1594 CFReleaseNull(accessGroup
);
1599 /* AUDIT[securityd](done):
1600 query (ok) is a caller provided dictionary, only its cf type has been checked.
1603 SecItemServerCopyMatching(CFDictionaryRef query
, CFTypeRef
*result
,
1604 SecurityClient
*client
, CFErrorRef
*error
)
1606 CFArrayRef accessGroups
= CFRetainSafe(client
->accessGroups
);
1609 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
))) {
1610 CFReleaseNull(accessGroups
);
1611 return SecEntitlementError(error
);
1614 SecSignpostStart(SecSignpostSecItemCopyMatching
);
1616 if (client
->canAccessNetworkExtensionAccessGroups
) {
1617 CFDataRef persistentRef
= CFDictionaryGetValue(query
, kSecValuePersistentRef
);
1618 CFStringRef itemClass
= NULL
;
1619 sqlite_int64 itemRowID
= 0;
1620 if (persistentRef
&& _SecItemParsePersistentRef(persistentRef
, &itemClass
, &itemRowID
, NULL
)) {
1621 CFStringRef accessGroup
= CopyAccessGroupForRowID(itemRowID
, itemClass
);
1622 if (accessGroup
&& CFStringHasSuffix(accessGroup
, kSecNetworkExtensionAccessGroupSuffix
) && !CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), accessGroup
)) {
1623 CFMutableArrayRef mutableAccessGroups
= CFArrayCreateMutableCopy(NULL
, 0, accessGroups
);
1624 CFArrayAppendValue(mutableAccessGroups
, accessGroup
);
1625 CFReleaseNull(accessGroups
);
1626 accessGroups
= mutableAccessGroups
;
1628 CFReleaseNull(accessGroup
);
1632 if (CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), CFSTR("*"))) {
1633 /* Having the special accessGroup "*" allows access to all accessGroups. */
1634 CFReleaseNull(accessGroups
);
1638 Query
*q
= query_create_with_limit(query
, client
->musr
, 1, error
);
1640 CFStringRef agrp
= CFDictionaryGetValue(q
->q_item
, kSecAttrAccessGroup
);
1642 if (accessGroupsAllows(accessGroups
, agrp
, client
)) {
1643 const void *val
= agrp
;
1644 CFReleaseNull(accessGroups
);
1645 accessGroups
= CFArrayCreate(0, &val
, 1, &kCFTypeArrayCallBacks
);
1647 (void)SecEntitlementErrorForExplicitAccessGroup(agrp
, accessGroups
, error
);
1648 CFReleaseNull(accessGroups
);
1649 query_destroy(q
, NULL
);
1654 if (accessGroups
!= NULL
) {
1655 // On iOS, drop 'com.apple.token' AG from allowed accessGroups, to avoid inserting token elements into
1656 // unsuspecting application's keychain. If the application on iOS wants to access token items, it needs
1657 // explicitly specify kSecAttrAccessGroup=kSecAttrAccessGroupToken in its query.
1658 CFMutableArrayRef mutableGroups
= CFArrayCreateMutableCopy(kCFAllocatorDefault
, 0, accessGroups
);
1659 CFArrayRemoveAllValue(mutableGroups
, kSecAttrAccessGroupToken
);
1660 CFReleaseNull(accessGroups
);
1661 accessGroups
= mutableGroups
;
1666 #if TARGET_OS_IPHONE
1667 if (q
->q_sync_bubble
&& client
->inMultiUser
) {
1668 CFReleaseNull(q
->q_musrView
);
1669 q
->q_musrView
= SecMUSRCreateSyncBubbleUserUUID(q
->q_sync_bubble
);
1670 } else if (client
->inMultiUser
&& client
->isNetworkExtension
) {
1671 CFReleaseNull(q
->q_musrView
);
1672 q
->q_musrView
= SecMUSRCreateBothUserAndSystemUUID(client
->uid
);
1673 } else if (q
->q_system_keychain
&& client
->inMultiUser
) {
1674 CFReleaseNull(q
->q_musrView
);
1675 q
->q_musrView
= SecMUSRCopySystemKeychainUUID();
1677 q
->q_system_keychain
= false;
1681 query_set_caller_access_groups(q
, accessGroups
);
1683 /* Sanity check the query. */
1684 if (q
->q_system_keychain
&& !client
->allowSystemKeychain
) {
1685 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for system keychain"));
1686 } else if (q
->q_sync_bubble
&& !client
->allowSyncBubbleKeychain
) {
1687 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for syncbubble keychain"));
1688 } else if (q
->q_system_keychain
&& q
->q_sync_bubble
) {
1689 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("can't do both system and syncbubble keychain"));
1690 } else if (q
->q_use_item_list
) {
1691 ok
= SecError(errSecUseItemListUnsupported
, error
, CFSTR("use item list unsupported"));
1692 } else if (q
->q_match_issuer
&& ((q
->q_class
!= cert_class()) &&
1693 (q
->q_class
!= identity_class()))) {
1694 ok
= SecError(errSecUnsupportedOperation
, error
, CFSTR("unsupported match attribute"));
1695 } else if (q
->q_match_policy
&& ((q
->q_class
!= cert_class()) &&
1696 (q
->q_class
!= identity_class()))) {
1697 ok
= SecError(errSecUnsupportedOperation
, error
, CFSTR("unsupported kSecMatchPolicy attribute"));
1698 } else if (q
->q_return_type
!= 0 && result
== NULL
) {
1699 ok
= SecError(errSecReturnMissingPointer
, error
, CFSTR("missing pointer"));
1700 } else if (!q
->q_error
) {
1701 ok
= kc_with_dbt(false, error
, ^(SecDbConnectionRef dbt
) {
1702 return s3dl_copy_matching(dbt
, q
, result
, accessGroups
, error
);
1706 if (!query_destroy(q
, error
))
1709 CFReleaseNull(accessGroups
);
1711 SecSignpostStop(SecSignpostSecItemCopyMatching
);
1717 _SecItemCopyMatching(CFDictionaryRef query
, SecurityClient
*client
, CFTypeRef
*result
, CFErrorRef
*error
) {
1718 return SecItemServerCopyMatching(query
, result
, client
, error
);
1721 #if TARGET_OS_IPHONE
1723 SecItemSynchronizable(CFDictionaryRef query
)
1725 bool result
= false;
1726 CFTypeRef value
= CFDictionaryGetValue(query
, kSecAttrSynchronizable
);
1727 if (isBoolean(value
))
1728 return CFBooleanGetValue(value
);
1729 else if (isNumber(value
)) {
1731 (void)CFNumberGetValue(value
, kCFNumberSInt32Type
, &number
);
1740 SecurityClientCopyWritableAccessGroups(SecurityClient
*client
) {
1741 if (client
== NULL
|| client
->accessGroups
== NULL
) {
1744 CFIndex count
= CFArrayGetCount(client
->accessGroups
);
1745 if (CFArrayContainsValue(client
->accessGroups
, CFRangeMake(0, count
), kSecAttrAccessGroupToken
)) {
1746 CFMutableArrayRef writableGroups
= CFArrayCreateMutableCopy(kCFAllocatorDefault
, 0, client
->accessGroups
);
1747 CFArrayRemoveAllValue(writableGroups
, kSecAttrAccessGroupToken
);
1748 return writableGroups
;
1750 return CFRetainSafe(client
->accessGroups
);
1755 /* AUDIT[securityd](done):
1756 attributes (ok) is a caller provided dictionary, only its cf type has
1760 _SecItemAdd(CFDictionaryRef attributes
, SecurityClient
*client
, CFTypeRef
*result
, CFErrorRef
*error
)
1762 CFArrayRef accessGroups
= SecurityClientCopyWritableAccessGroups(client
);
1766 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
))) {
1767 CFReleaseNull(accessGroups
);
1768 return SecEntitlementError(error
);
1771 SecSignpostStart(SecSignpostSecItemAdd
);
1773 Query
*q
= query_create_with_limit(attributes
, client
->musr
, 0, error
);
1775 /* Access group sanity checking. */
1776 CFStringRef agrp
= (CFStringRef
)CFDictionaryGetValue(attributes
,
1777 kSecAttrAccessGroup
);
1779 /* Having the special accessGroup "*" allows access to all accessGroups. */
1780 if (CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), CFSTR("*")))
1781 CFReleaseNull(accessGroups
);
1784 /* The user specified an explicit access group, validate it. */
1785 if (!accessGroupsAllows(accessGroups
, agrp
, client
))
1786 ok
= SecEntitlementErrorForExplicitAccessGroup(agrp
, accessGroups
, error
);
1788 agrp
= (CFStringRef
)CFArrayGetValueAtIndex(client
->accessGroups
, 0);
1790 /* We are using an implicit access group, add it as if the user
1791 specified it as an attribute. */
1792 query_add_attribute(kSecAttrAccessGroup
, agrp
, q
);
1795 if (SecPLShouldLogRegisteredEvent(CFSTR("SecItem"))) {
1796 CFDictionaryRef dict
= CFDictionaryCreateForCFTypes(NULL
, CFSTR("operation"), CFSTR("add"), CFSTR("AccessGroup"), agrp
, NULL
);
1798 SecPLLogRegisteredEvent(CFSTR("SecItem"), dict
);
1802 #if TARGET_OS_IPHONE
1803 if (q
->q_system_keychain
&& client
->inMultiUser
) {
1804 CFReleaseNull(q
->q_musrView
);
1805 q
->q_musrView
= SecMUSRCopySystemKeychainUUID();
1807 q
->q_system_keychain
= false;
1809 query_add_attribute_with_desc(&v8musr
, q
->q_musrView
, q
);
1813 query_ensure_access_control(q
, agrp
);
1816 void (^add_sync_callback
)(bool, CFErrorRef
) = CFDictionaryGetValue(attributes
, CFSTR("f_ckkscallback"));
1817 if(add_sync_callback
) {
1818 // The existence of this callback indicates that we need a predictable UUID for this item.
1819 q
->q_uuid_from_primary_key
= true;
1820 q
->q_add_sync_callback
= add_sync_callback
;
1824 if (q
->q_system_keychain
&& !client
->allowSystemKeychain
) {
1825 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for system keychain"));
1826 } else if (q
->q_sync_bubble
&& !client
->allowSyncBubbleKeychain
) {
1827 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for syncbubble keychain"));
1828 #if TARGET_OS_IPHONE
1829 } else if (q
->q_system_keychain
&& SecItemSynchronizable(attributes
) && !client
->inMultiUser
) {
1830 ok
= SecError(errSecInvalidKey
, error
, CFSTR("Can't store system keychain and synchronizable"));
1832 } else if (q
->q_row_id
|| q
->q_token_object_id
) {
1833 ok
= SecError(errSecValuePersistentRefUnsupported
, error
, CFSTR("q_row_id")); // TODO: better error string
1834 } else if (!q
->q_error
) {
1835 ok
= kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
){
1836 return kc_transaction(dbt
, error
, ^{
1837 query_pre_add(q
, true);
1838 return s3dl_query_add(dbt
, q
, result
, error
);
1843 ok
= query_notify_and_destroy(q
, ok
, error
);
1847 CFReleaseNull(accessGroups
);
1849 SecSignpostStop(SecSignpostSecItemAdd
);
1854 /* AUDIT[securityd](done):
1855 query (ok) and attributesToUpdate (ok) are a caller provided dictionaries,
1856 only their cf types have been checked.
1859 _SecItemUpdate(CFDictionaryRef query
, CFDictionaryRef attributesToUpdate
,
1860 SecurityClient
*client
, CFErrorRef
*error
)
1862 CFArrayRef accessGroups
= SecurityClientCopyWritableAccessGroups(client
);
1865 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
))) {
1866 CFReleaseNull(accessGroups
);
1867 return SecEntitlementError(error
);
1870 // Queries using implicit access groups which only find items that're inaccessible yield errSecItemNotFound,
1871 // but we can pre-emptively shut down queries which are clearly illegal
1872 CFTypeRef q_agrp
= CFDictionaryGetValue(query
, kSecAttrAccessGroup
);
1873 if (q_agrp
&& !accessGroupsAllows(accessGroups
, q_agrp
, client
)) {
1874 SecEntitlementErrorForExplicitAccessGroup(q_agrp
, accessGroups
, error
);
1875 CFReleaseSafe(accessGroups
);
1879 SecSignpostStart(SecSignpostSecItemUpdate
);
1881 if (SecPLShouldLogRegisteredEvent(CFSTR("SecItem"))) {
1882 CFTypeRef agrp
= CFArrayGetValueAtIndex(accessGroups
, 0);
1883 CFDictionaryRef dict
= CFDictionaryCreateForCFTypes(NULL
, CFSTR("operation"), CFSTR("update"), CFSTR("AccessGroup"), agrp
, NULL
);
1885 SecPLLogRegisteredEvent(CFSTR("SecItem"), dict
);
1890 if (CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), CFSTR("*"))) {
1891 /* Having the special accessGroup "*" allows access to all accessGroups. */
1892 CFReleaseNull(accessGroups
);
1896 Query
*q
= query_create_with_limit(query
, client
->musr
, kSecMatchUnlimited
, error
);
1901 #if TARGET_OS_IPHONE
1902 if (q
->q_system_keychain
&& client
->inMultiUser
) {
1903 CFReleaseNull(q
->q_musrView
);
1904 q
->q_musrView
= SecMUSRCopySystemKeychainUUID();
1906 q
->q_system_keychain
= false;
1910 /* Sanity check the query. */
1911 query_set_caller_access_groups(q
, accessGroups
);
1912 if (q
->q_system_keychain
&& !client
->allowSystemKeychain
) {
1913 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for system keychain"));
1914 } else if (q
->q_sync_bubble
&& !client
->allowSyncBubbleKeychain
) {
1915 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for syncbubble keychain"));
1916 #if TARGET_OS_IPHONE
1917 } else if (q
->q_system_keychain
&& SecItemSynchronizable(attributesToUpdate
) && !client
->inMultiUser
) {
1918 ok
= SecError(errSecInvalidKey
, error
, CFSTR("Can't update an system keychain item with synchronizable"));
1920 } else if (q
->q_use_item_list
) {
1921 ok
= SecError(errSecUseItemListUnsupported
, error
, CFSTR("use item list not supported"));
1922 } else if (q
->q_return_type
& kSecReturnDataMask
) {
1923 /* Update doesn't return anything so don't ask for it. */
1924 ok
= SecError(errSecReturnDataUnsupported
, error
, CFSTR("return data not supported by update"));
1925 } else if (q
->q_return_type
& kSecReturnAttributesMask
) {
1926 ok
= SecError(errSecReturnAttributesUnsupported
, error
, CFSTR("return attributes not supported by update"));
1927 } else if (q
->q_return_type
& kSecReturnRefMask
) {
1928 ok
= SecError(errSecReturnRefUnsupported
, error
, CFSTR("return ref not supported by update"));
1929 } else if (q
->q_return_type
& kSecReturnPersistentRefMask
) {
1930 ok
= SecError(errSecReturnPersistentRefUnsupported
, error
, CFSTR("return persistent ref not supported by update"));
1932 /* Access group sanity checking. */
1933 CFStringRef agrp
= (CFStringRef
)CFDictionaryGetValue(attributesToUpdate
,
1934 kSecAttrAccessGroup
);
1936 /* The user is attempting to modify the access group column,
1937 validate it to make sure the new value is allowable. */
1938 if (!accessGroupsAllows(accessGroups
, agrp
, client
)) {
1939 secerror("Cannot update keychain item to access group %@", agrp
);
1940 ok
= SecEntitlementErrorForExplicitAccessGroup(agrp
, accessGroups
, error
);
1946 ok
= kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
1947 return kc_transaction(dbt
, error
, ^{
1948 return s3dl_query_update(dbt
, q
, attributesToUpdate
, accessGroups
, error
);
1953 ok
= query_notify_and_destroy(q
, ok
, error
);
1955 CFReleaseNull(accessGroups
);
1957 SecSignpostStop(SecSignpostSecItemUpdate
);
1963 /* AUDIT[securityd](done):
1964 query (ok) is a caller provided dictionary, only its cf type has been checked.
1967 _SecItemDelete(CFDictionaryRef query
, SecurityClient
*client
, CFErrorRef
*error
)
1969 CFArrayRef accessGroups
= SecurityClientCopyWritableAccessGroups(client
);
1972 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
))) {
1973 CFReleaseNull(accessGroups
);
1974 return SecEntitlementError(error
);
1977 CFTypeRef q_agrp
= CFDictionaryGetValue(query
, kSecAttrAccessGroup
);
1978 if (q_agrp
&& !accessGroupsAllows(accessGroups
, q_agrp
, client
)) {
1979 SecEntitlementErrorForExplicitAccessGroup(q_agrp
, accessGroups
, error
);
1980 CFReleaseSafe(accessGroups
);
1984 SecSignpostStart(SecSignpostSecItemDelete
);
1986 if (SecPLShouldLogRegisteredEvent(CFSTR("SecItem"))) {
1987 CFTypeRef agrp
= CFArrayGetValueAtIndex(accessGroups
, 0);
1988 CFDictionaryRef dict
= CFDictionaryCreateForCFTypes(NULL
, CFSTR("operation"), CFSTR("delete"), CFSTR("AccessGroup"), agrp
, NULL
);
1990 SecPLLogRegisteredEvent(CFSTR("SecItem"), dict
);
1995 if (CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), CFSTR("*"))) {
1996 /* Having the special accessGroup "*" allows access to all accessGroups. */
1997 CFReleaseNull(accessGroups
);
2000 Query
*q
= query_create_with_limit(query
, client
->musr
, kSecMatchUnlimited
, error
);
2003 #if TARGET_OS_IPHONE
2004 if (q
->q_system_keychain
&& client
->inMultiUser
) {
2005 CFReleaseNull(q
->q_musrView
);
2006 q
->q_musrView
= SecMUSRCopySystemKeychainUUID();
2008 q
->q_system_keychain
= false;
2012 query_set_caller_access_groups(q
, accessGroups
);
2013 /* Sanity check the query. */
2014 if (q
->q_system_keychain
&& !client
->allowSystemKeychain
) {
2015 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for system keychain"));
2016 } else if (q
->q_sync_bubble
&& !client
->allowSyncBubbleKeychain
) {
2017 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for syncbubble keychain"));
2018 } else if (q
->q_limit
!= kSecMatchUnlimited
) {
2019 ok
= SecError(errSecMatchLimitUnsupported
, error
, CFSTR("match limit not supported by delete"));
2020 } else if (query_match_count(q
) != 0) {
2021 ok
= SecError(errSecItemMatchUnsupported
, error
, CFSTR("match not supported by delete"));
2022 } else if (q
->q_ref
) {
2023 ok
= SecError(errSecValueRefUnsupported
, error
, CFSTR("value ref not supported by delete"));
2024 } else if (q
->q_row_id
&& query_attr_count(q
)) {
2025 ok
= SecError(errSecItemIllegalQuery
, error
, CFSTR("rowid and other attributes are mutually exclusive"));
2026 } else if (q
->q_token_object_id
&& query_attr_count(q
) != 1) {
2027 ok
= SecError(errSecItemIllegalQuery
, error
, CFSTR("token persistent ref and other attributes are mutually exclusive"));
2029 ok
= kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
2030 return kc_transaction(dbt
, error
, ^{
2031 return s3dl_query_delete(dbt
, q
, accessGroups
, error
);
2035 ok
= query_notify_and_destroy(q
, ok
, error
);
2039 CFReleaseNull(accessGroups
);
2041 SecSignpostStop(SecSignpostSecItemDelete
);
2046 static bool SecItemDeleteTokenItems(SecDbConnectionRef dbt
, CFTypeRef classToDelete
, CFTypeRef tokenID
, CFArrayRef accessGroups
, SecurityClient
*client
, CFErrorRef
*error
) {
2047 CFTypeRef keys
[] = { kSecClass
, kSecAttrTokenID
};
2048 CFTypeRef values
[] = { classToDelete
, tokenID
};
2050 CFDictionaryRef query
= CFDictionaryCreate(kCFAllocatorDefault
, keys
, values
, 2, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2051 Query
*q
= query_create_with_limit(query
, client
->musr
, kSecMatchUnlimited
, error
);
2055 query_set_caller_access_groups(q
, accessGroups
);
2056 ok
= s3dl_query_delete(dbt
, q
, accessGroups
, error
);
2057 ok
= query_notify_and_destroy(q
, ok
, error
);
2065 static bool SecItemAddTokenItem(SecDbConnectionRef dbt
, CFDictionaryRef attributes
, CFArrayRef accessGroups
, SecurityClient
*client
, CFErrorRef
*error
) {
2067 Query
*q
= query_create_with_limit(attributes
, client
->musr
, 0, error
);
2069 CFStringRef agrp
= kSecAttrAccessGroupToken
;
2070 query_add_attribute(kSecAttrAccessGroup
, agrp
, q
);
2073 query_ensure_access_control(q
, agrp
);
2074 if (q
->q_system_keychain
&& !client
->allowSystemKeychain
) {
2075 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for system keychain"));
2076 } else if (q
->q_sync_bubble
&& !client
->allowSyncBubbleKeychain
) {
2077 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for syncbubble keychain"));
2078 } else if (q
->q_row_id
|| q
->q_token_object_id
) {
2079 ok
= SecError(errSecValuePersistentRefUnsupported
, error
, CFSTR("q_row_id")); // TODO: better error string
2080 } else if (!q
->q_error
) {
2081 query_pre_add(q
, true);
2082 ok
= s3dl_query_add(dbt
, q
, NULL
, error
);
2085 ok
= query_notify_and_destroy(q
, ok
, error
);
2092 bool _SecItemUpdateTokenItems(CFStringRef tokenID
, CFArrayRef items
, SecurityClient
*client
, CFErrorRef
*error
) {
2094 CFArrayRef accessGroups
= client
->accessGroups
;
2096 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
))) {
2097 return SecEntitlementError(error
);
2100 ok
= kc_with_dbt(true, error
, ^bool (SecDbConnectionRef dbt
) {
2101 return kc_transaction(dbt
, error
, ^bool {
2103 const CFTypeRef classToDelete
[] = { kSecClassGenericPassword
, kSecClassInternetPassword
, kSecClassCertificate
, kSecClassKey
};
2104 for (size_t i
= 0; i
< sizeof(classToDelete
) / sizeof(classToDelete
[0]); ++i
) {
2105 SecItemDeleteTokenItems(dbt
, classToDelete
[i
], tokenID
, accessGroups
, client
, NULL
);
2108 for (CFIndex i
= 0; i
< CFArrayGetCount(items
); ++i
) {
2109 if (!SecItemAddTokenItem(dbt
, CFArrayGetValueAtIndex(items
, i
), accessGroups
, client
, error
))
2115 const CFTypeRef classToDelete
[] = { kSecClassGenericPassword
, kSecClassInternetPassword
, kSecClassCertificate
, kSecClassKey
};
2116 bool deleted
= true;
2117 for (size_t i
= 0; i
< sizeof(classToDelete
) / sizeof(classToDelete
[0]); ++i
) {
2118 if (!SecItemDeleteTokenItems(dbt
, classToDelete
[i
], tokenID
, accessGroups
, client
, error
) && error
&& CFErrorGetCode(*error
) != errSecItemNotFound
) {
2122 else if (error
&& *error
) {
2123 CFReleaseNull(*error
);
2134 static bool deleteNonSysboundItemsForItemClass(SecDbConnectionRef dbt
, SecDbClass
const* class, CFErrorRef
* error
) {
2135 CFMutableDictionaryRef query
= CFDictionaryCreateMutableForCFTypes(NULL
);
2136 CFDictionaryAddValue(query
, kSecMatchLimit
, kSecMatchLimitAll
);
2138 __block CFErrorRef localError
= NULL
;
2139 SecDbQueryRef q
= query_create(class, NULL
, query
, &localError
);
2140 if (q
== NULL
) { // illegal query or out of memory
2141 secerror("SecItemServerDeleteAll: aborting because failed to initialize Query: %@", localError
);
2144 SecDbItemSelect(q
, dbt
, &localError
, ^bool(const SecDbAttr
*attr
) {
2145 return (attr
->flags
& kSecDbInFlag
) && !CFEqual(attr
->name
, CFSTR("data"));
2146 }, NULL
, NULL
, NULL
,
2147 ^(SecDbItemRef item
, bool *stop
) {
2148 if (!SecItemIsSystemBound(item
->attributes
, class, false) &&
2149 !CFEqual(CFDictionaryGetValue(item
->attributes
, kSecAttrAccessGroup
), CFSTR("com.apple.bluetooth")))
2151 SecDbItemDelete(item
, dbt
, kCFBooleanFalse
, &localError
);
2154 query_destroy(q
, &localError
);
2158 CFReleaseNull(*error
);
2159 *error
= localError
;
2161 CFReleaseNull(localError
);
2168 // Delete all the items except sysbound ones because horrible things happen if you do, like bluetooth devices unpairing
2170 SecItemServerDeleteAll(CFErrorRef
*error
) {
2171 secerror("SecItemServerDeleteAll");
2172 return kc_with_dbt(true, error
, ^bool (SecDbConnectionRef dbt
) {
2173 return (kc_transaction(dbt
, error
, ^bool {
2175 ok
&= SecDbExec(dbt
, CFSTR("DELETE FROM genp WHERE sync=1;"), error
);
2176 ok
&= SecDbExec(dbt
, CFSTR("DELETE FROM inet WHERE sync=1;"), error
);
2177 ok
&= SecDbExec(dbt
, CFSTR("DELETE FROM cert WHERE sync=1;"), error
);
2178 ok
&= SecDbExec(dbt
, CFSTR("DELETE FROM keys WHERE sync=1;"), error
);
2180 ok
&= deleteNonSysboundItemsForItemClass(dbt
, genp_class(), error
);
2181 ok
&= deleteNonSysboundItemsForItemClass(dbt
, inet_class(), error
);
2182 ok
&= deleteNonSysboundItemsForItemClass(dbt
, cert_class(), error
);
2183 ok
&= deleteNonSysboundItemsForItemClass(dbt
, keys_class(), error
);
2186 }) && SecDbExec(dbt
, CFSTR("VACUUM;"), error
));
2191 _SecItemDeleteAll(CFErrorRef
*error
) {
2192 return SecItemServerDeleteAll(error
);
2196 _SecItemServerDeleteAllWithAccessGroups(CFArrayRef accessGroups
, SecurityClient
*client
, CFErrorRef
*error
)
2198 __block
bool ok
= true;
2199 static dispatch_once_t onceToken
;
2200 static CFSetRef illegalAccessGroups
= NULL
;
2202 dispatch_once(&onceToken
, ^{
2203 const CFStringRef values
[] = {
2206 CFSTR("com.apple.security.sos"),
2207 CFSTR("lockdown-identities"),
2209 illegalAccessGroups
= CFSetCreate(NULL
, (const void **)values
, sizeof(values
)/sizeof(values
[0]), &kCFTypeSetCallBacks
);
2212 static CFTypeRef qclasses
[] = {
2218 // strange construction needed for schema indirection
2219 static dispatch_once_t qclassesOnceToken
;
2220 dispatch_once(&qclassesOnceToken
, ^{
2221 qclasses
[0] = inet_class();
2222 qclasses
[1] = genp_class();
2223 qclasses
[2] = keys_class();
2224 qclasses
[3] = cert_class();
2227 require_action_quiet(isArray(accessGroups
), fail
,
2229 SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType
, sSecXPCErrorDomain
, NULL
, error
, NULL
, CFSTR("accessGroups not CFArray, got %@"), accessGroups
));
2231 // TODO: whitelist instead? look for dev IDs like 7123498YQX.com.somedev.app
2233 require_action(CFArrayGetCount(accessGroups
) != 0, fail
,
2235 SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType
, sSecXPCErrorDomain
, NULL
, error
, NULL
, CFSTR("accessGroups e empty")));
2238 // Pre-check accessGroups for prohibited values
2239 CFArrayForEach(accessGroups
, ^(const void *value
) {
2240 CFStringRef agrp
= (CFStringRef
)value
;
2242 if (!isString(agrp
)) {
2243 SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType
, sSecXPCErrorDomain
, NULL
, error
, NULL
,
2244 CFSTR("access not a string: %@"), agrp
);
2246 } else if (CFSetContainsValue(illegalAccessGroups
, agrp
)) {
2247 SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType
, sSecXPCErrorDomain
, NULL
, error
, NULL
,
2248 CFSTR("illegal access group: %@"), accessGroups
);
2254 ok
= kc_with_dbt(true, error
, ^bool(SecDbConnectionRef dbt
) {
2255 return kc_transaction(dbt
, error
, ^bool {
2256 CFErrorRef localError
= NULL
;
2260 for (n
= 0; n
< sizeof(qclasses
)/sizeof(qclasses
[0]) && ok1
; n
++) {
2263 q
= query_create(qclasses
[n
], client
->musr
, NULL
, error
);
2266 (void)s3dl_query_delete(dbt
, q
, accessGroups
, &localError
);
2268 query_destroy(q
, error
);
2269 CFReleaseNull(localError
);
2272 }) && SecDbExec(dbt
, CFSTR("VACUUM"), error
);
2281 // MARK: Shared web credentials
2283 #if SHAREDWEBCREDENTIALS
2286 #define SEC_CONST_DECL(k,v) const CFStringRef k = CFSTR(v);
2288 SEC_CONST_DECL (kSecSafariAccessGroup
, "com.apple.cfnetwork");
2289 SEC_CONST_DECL (kSecSafariDefaultComment
, "default");
2290 SEC_CONST_DECL (kSecSafariPasswordsNotSaved
, "Passwords not saved");
2291 SEC_CONST_DECL (kSecSharedCredentialUrlScheme
, "https://");
2292 SEC_CONST_DECL (kSecSharedWebCredentialsService
, "webcredentials");
2294 #if !TARGET_OS_SIMULATOR
2296 _SecAppDomainApprovalStatus(CFStringRef appID
, CFStringRef fqdn
, CFErrorRef
*error
)
2298 __block SWCFlags flags
= kSWCFlags_None
;
2301 secnotice("swc", "Application %@ is requesting approval for %@", appID
, fqdn
);
2303 dispatch_semaphore_t semaphore
= dispatch_semaphore_create(0);
2304 if (semaphore
== NULL
)
2307 status
= SWCCheckService(kSecSharedWebCredentialsService
, appID
, fqdn
, ^void (OSStatus inStatus
, SWCFlags inFlags
, CFDictionaryRef inDetails
)
2309 if (inStatus
== 0) {
2312 secerror("SWCCheckService failed with %d", (int)inStatus
);
2314 dispatch_semaphore_signal(semaphore
);
2318 dispatch_semaphore_wait(semaphore
, DISPATCH_TIME_FOREVER
);
2320 secerror("SWCCheckService: failed to queue");
2322 dispatch_release(semaphore
);
2325 if (!(flags
& kSWCFlag_SiteApproved
)) {
2326 SecError(errSecAuthFailed
, error
, CFSTR("\"%@\" failed to approve \"%@\""), fqdn
, appID
);
2327 } else if (flags
& kSWCFlag_UserDenied
) {
2328 SecError(errSecAuthFailed
, error
, CFSTR("User denied access to \"%@\" by \"%@\""), fqdn
, appID
);
2335 _SecEntitlementContainsDomainForService(CFArrayRef domains
, CFStringRef domain
, CFStringRef service
)
2337 bool result
= false;
2338 CFIndex idx
, count
= (domains
) ? CFArrayGetCount(domains
) : (CFIndex
) 0;
2339 if (!count
|| !domain
|| !service
) {
2342 for (idx
=0; idx
< count
; idx
++) {
2343 CFStringRef str
= (CFStringRef
) CFArrayGetValueAtIndex(domains
, idx
);
2344 if (str
&& CFStringHasPrefix(str
, kSecSharedWebCredentialsService
)) {
2345 CFIndex prefix_len
= CFStringGetLength(kSecSharedWebCredentialsService
)+1;
2346 CFIndex substr_len
= CFStringGetLength(str
) - prefix_len
;
2347 CFRange range
= { prefix_len
, substr_len
};
2348 CFStringRef substr
= CFStringCreateWithSubstring(kCFAllocatorDefault
, str
, range
);
2349 if (substr
&& CFEqual(substr
, domain
)) {
2352 CFReleaseSafe(substr
);
2360 #endif /* !TARGET_OS_SIMULATOR */
2363 _SecAddNegativeWebCredential(SecurityClient
*client
, CFStringRef fqdn
, CFStringRef appID
, bool forSafari
)
2365 #if !TARGET_OS_SIMULATOR
2366 bool result
= false;
2367 if (!fqdn
) { return result
; }
2369 // update our database
2370 CFRetainSafe(appID
);
2372 if (0 == SWCSetServiceFlags(kSecSharedWebCredentialsService
, appID
, fqdn
, kSWCFlag_ExternalMask
, kSWCFlag_UserDenied
,
2373 ^void(OSStatus inStatus
, SWCFlags inNewFlags
){
2374 CFReleaseSafe(appID
);
2375 CFReleaseSafe(fqdn
);
2380 else // didn't queue the block
2382 CFReleaseSafe(appID
);
2383 CFReleaseSafe(fqdn
);
2386 if (!forSafari
) { return result
; }
2388 // below this point: create a negative Safari web credential item
2390 CFMutableDictionaryRef attrs
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2391 if (!attrs
) { return result
; }
2393 CFErrorRef error
= NULL
;
2394 CFStringRef accessGroup
= CFSTR("*");
2395 SecurityClient swcclient
= {
2397 .accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&accessGroup
, 1, &kCFTypeArrayCallBacks
),
2398 .allowSystemKeychain
= false,
2399 .allowSyncBubbleKeychain
= false,
2400 .isNetworkExtension
= false,
2401 .musr
= client
->musr
,
2404 CFDictionaryAddValue(attrs
, kSecClass
, kSecClassInternetPassword
);
2405 CFDictionaryAddValue(attrs
, kSecAttrAccessGroup
, kSecSafariAccessGroup
);
2406 CFDictionaryAddValue(attrs
, kSecAttrAuthenticationType
, kSecAttrAuthenticationTypeHTMLForm
);
2407 CFDictionaryAddValue(attrs
, kSecAttrProtocol
, kSecAttrProtocolHTTPS
);
2408 CFDictionaryAddValue(attrs
, kSecAttrServer
, fqdn
);
2409 CFDictionaryAddValue(attrs
, kSecAttrSynchronizable
, kCFBooleanTrue
);
2411 (void)_SecItemDelete(attrs
, &swcclient
, &error
);
2412 CFReleaseNull(error
);
2414 CFDictionaryAddValue(attrs
, kSecAttrAccount
, kSecSafariPasswordsNotSaved
);
2415 CFDictionaryAddValue(attrs
, kSecAttrComment
, kSecSafariDefaultComment
);
2417 CFStringRef label
= CFStringCreateWithFormat(kCFAllocatorDefault
,
2418 NULL
, CFSTR("%@ (%@)"), fqdn
, kSecSafariPasswordsNotSaved
);
2420 CFDictionaryAddValue(attrs
, kSecAttrLabel
, label
);
2421 CFReleaseSafe(label
);
2425 CFDataRef data
= CFDataCreate(kCFAllocatorDefault
, &space
, 1);
2427 CFDictionarySetValue(attrs
, kSecValueData
, data
);
2428 CFReleaseSafe(data
);
2431 CFTypeRef addResult
= NULL
;
2432 result
= _SecItemAdd(attrs
, &swcclient
, &addResult
, &error
);
2434 CFReleaseSafe(addResult
);
2435 CFReleaseSafe(error
);
2436 CFReleaseSafe(attrs
);
2437 CFReleaseSafe(swcclient
.accessGroups
);
2445 /* Specialized version of SecItemAdd for shared web credentials */
2447 _SecAddSharedWebCredential(CFDictionaryRef attributes
,
2448 SecurityClient
*client
,
2449 const audit_token_t
*clientAuditToken
,
2456 SecurityClient swcclient
= {};
2458 CFStringRef fqdn
= CFRetainSafe(CFDictionaryGetValue(attributes
, kSecAttrServer
));
2459 CFStringRef account
= CFDictionaryGetValue(attributes
, kSecAttrAccount
);
2460 #if TARGET_OS_IOS && !TARGET_OS_BRIDGE
2461 CFStringRef password
= CFDictionaryGetValue(attributes
, kSecSharedPassword
);
2463 CFStringRef password
= CFDictionaryGetValue(attributes
, CFSTR("spwd"));
2465 CFStringRef accessGroup
= CFSTR("*");
2466 CFMutableDictionaryRef query
= NULL
, attrs
= NULL
;
2470 // check autofill enabled status
2471 if (!swca_autofill_enabled(clientAuditToken
)) {
2472 SecError(errSecBadReq
, error
, CFSTR("Autofill is not enabled in Safari settings"));
2476 // parse fqdn with CFURL here, since it could be specified as domain:port
2478 CFStringRef urlStr
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%@%@"), kSecSharedCredentialUrlScheme
, fqdn
);
2480 CFURLRef url
= CFURLCreateWithString(kCFAllocatorDefault
, urlStr
, nil
);
2482 CFStringRef hostname
= CFURLCopyHostName(url
);
2484 CFReleaseSafe(fqdn
);
2486 port
= CFURLGetPortNumber(url
);
2490 CFReleaseSafe(urlStr
);
2495 SecError(errSecParam
, error
, CFSTR("No account provided"));
2499 SecError(errSecParam
, error
, CFSTR("No domain provided"));
2503 #if TARGET_OS_SIMULATOR
2504 secerror("app/site association entitlements not checked in Simulator");
2506 OSStatus status
= errSecMissingEntitlement
;
2507 // validate that fqdn is part of caller's shared credential domains entitlement
2509 SecError(status
, error
, CFSTR("Missing application-identifier entitlement"));
2512 if (_SecEntitlementContainsDomainForService(domains
, fqdn
, kSecSharedWebCredentialsService
)) {
2513 status
= errSecSuccess
;
2515 if (errSecSuccess
!= status
) {
2516 CFStringRef msg
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
,
2517 CFSTR("%@ not found in %@ entitlement"), fqdn
, kSecEntitlementAssociatedDomains
);
2519 msg
= CFRetain(CFSTR("Requested domain not found in entitlement"));
2521 SecError(status
, error
, CFSTR("%@"), msg
);
2527 #if TARGET_OS_SIMULATOR
2528 secerror("Ignoring app/site approval state in the Simulator.");
2530 // get approval status for this app/domain pair
2531 SWCFlags flags
= _SecAppDomainApprovalStatus(appID
, fqdn
, error
);
2532 if (!(flags
& kSWCFlag_SiteApproved
)) {
2537 // give ourselves access to see matching items for kSecSafariAccessGroup
2538 swcclient
.task
= NULL
;
2539 swcclient
.accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&accessGroup
, 1, &kCFTypeArrayCallBacks
);
2540 swcclient
.allowSystemKeychain
= false;
2541 swcclient
.musr
= client
->musr
;
2542 swcclient
.allowSystemKeychain
= false;
2543 swcclient
.allowSyncBubbleKeychain
= false;
2544 swcclient
.isNetworkExtension
= false;
2547 // create lookup query
2548 query
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2550 SecError(errSecAllocate
, error
, CFSTR("Unable to create query dictionary"));
2553 CFDictionaryAddValue(query
, kSecClass
, kSecClassInternetPassword
);
2554 CFDictionaryAddValue(query
, kSecAttrAccessGroup
, kSecSafariAccessGroup
);
2555 CFDictionaryAddValue(query
, kSecAttrAuthenticationType
, kSecAttrAuthenticationTypeHTMLForm
);
2556 CFDictionaryAddValue(query
, kSecAttrServer
, fqdn
);
2557 CFDictionaryAddValue(query
, kSecAttrSynchronizable
, kCFBooleanTrue
);
2559 // check for presence of Safari's negative entry ('passwords not saved')
2560 CFDictionarySetValue(query
, kSecAttrAccount
, kSecSafariPasswordsNotSaved
);
2561 ok
= _SecItemCopyMatching(query
, &swcclient
, result
, error
);
2562 if(result
) CFReleaseNull(*result
);
2563 if (error
) CFReleaseNull(*error
);
2565 SecError(errSecDuplicateItem
, error
, CFSTR("Item already exists for this server"));
2569 // now use the provided account (and optional port number, if one was present)
2570 CFDictionarySetValue(query
, kSecAttrAccount
, account
);
2571 if (port
< -1 || port
> 0) {
2572 SInt16 portValueShort
= (port
& 0xFFFF);
2573 CFNumberRef portNumber
= CFNumberCreate(NULL
, kCFNumberSInt16Type
, &portValueShort
);
2574 CFDictionaryAddValue(query
, kSecAttrPort
, portNumber
);
2575 CFReleaseSafe(portNumber
);
2578 // look up existing password
2579 CFDictionaryAddValue(query
, kSecReturnData
, kCFBooleanTrue
);
2580 bool matched
= _SecItemCopyMatching(query
, &swcclient
, result
, error
);
2581 CFDictionaryRemoveValue(query
, kSecReturnData
);
2583 // found it, so this becomes either an "update password" or "delete password" operation
2584 bool update
= (password
!= NULL
);
2586 attrs
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2587 CFDataRef credential
= CFStringCreateExternalRepresentation(kCFAllocatorDefault
, password
, kCFStringEncodingUTF8
, 0);
2588 CFDictionaryAddValue(attrs
, kSecValueData
, credential
);
2589 bool samePassword
= result
&& *result
&& CFEqual(*result
, credential
);
2590 CFReleaseSafe(credential
);
2591 CFDictionaryAddValue(attrs
, kSecAttrComment
, kSecSafariDefaultComment
);
2593 ok
= samePassword
|| swca_confirm_operation(swca_update_request_id
, clientAuditToken
, query
, error
,
2594 ^void (CFStringRef confirm_fqdn
) {
2595 _SecAddNegativeWebCredential(client
, confirm_fqdn
, appID
, false);
2598 ok
= _SecItemUpdate(query
, attrs
, &swcclient
, error
);
2602 // confirm the delete
2603 // (per rdar://16676288 we always prompt, even if there was prior user approval)
2604 ok
= /*approved ||*/ swca_confirm_operation(swca_delete_request_id
, clientAuditToken
, query
, error
,
2605 ^void (CFStringRef confirm_fqdn
) {
2606 _SecAddNegativeWebCredential(client
, confirm_fqdn
, appID
, false);
2609 ok
= _SecItemDelete(query
, &swcclient
, error
);
2613 if(result
) CFReleaseNull(*result
);
2614 if(error
) CFReleaseNull(*error
);
2618 if (result
) CFReleaseNull(*result
);
2619 if (error
) CFReleaseNull(*error
);
2621 // password does not exist, so prepare to add it
2623 // a NULL password value removes the existing credential. Since we didn't find it, this is a no-op.
2628 CFStringRef label
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%@ (%@)"), fqdn
, account
);
2630 CFDictionaryAddValue(query
, kSecAttrLabel
, label
);
2631 CFReleaseSafe(label
);
2633 // NOTE: we always expect to use HTTPS for web forms.
2634 CFDictionaryAddValue(query
, kSecAttrProtocol
, kSecAttrProtocolHTTPS
);
2636 CFDataRef credential
= CFStringCreateExternalRepresentation(kCFAllocatorDefault
, password
, kCFStringEncodingUTF8
, 0);
2637 CFDictionarySetValue(query
, kSecValueData
, credential
);
2638 CFReleaseSafe(credential
);
2639 CFDictionarySetValue(query
, kSecAttrComment
, kSecSafariDefaultComment
);
2641 CFReleaseSafe(swcclient
.accessGroups
);
2642 swcclient
.accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&kSecSafariAccessGroup
, 1, &kCFTypeArrayCallBacks
);
2644 // mark the item as created by this function
2645 const int32_t creator_value
= 'swca';
2646 CFNumberRef creator
= CFNumberCreate(kCFAllocatorDefault
, kCFNumberSInt32Type
, &creator_value
);
2648 CFDictionarySetValue(query
, kSecAttrCreator
, creator
);
2649 CFReleaseSafe(creator
);
2653 ok
= swca_confirm_operation(swca_add_request_id
, clientAuditToken
, query
, error
, ^void (CFStringRef confirm_fqdn
) {
2654 _SecAddNegativeWebCredential(client
, confirm_fqdn
, appID
, false);
2658 ok
= _SecItemAdd(query
, &swcclient
, result
, error
);
2662 CFReleaseSafe(attrs
);
2663 CFReleaseSafe(query
);
2664 CFReleaseSafe(swcclient
.accessGroups
);
2665 CFReleaseSafe(fqdn
);
2669 /* Specialized version of SecItemCopyMatching for shared web credentials */
2671 _SecCopySharedWebCredential(CFDictionaryRef query
,
2672 SecurityClient
*client
,
2673 const audit_token_t
*clientAuditToken
,
2679 CFMutableArrayRef credentials
= NULL
;
2680 CFMutableArrayRef foundItems
= NULL
;
2681 CFMutableArrayRef fqdns
= NULL
;
2682 CFStringRef fqdn
= NULL
;
2683 CFStringRef account
= NULL
;
2687 require_quiet(result
, cleanup
);
2688 credentials
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
2689 foundItems
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
2690 fqdns
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
2692 // give ourselves access to see matching items for kSecSafariAccessGroup
2693 CFStringRef accessGroup
= CFSTR("*");
2694 SecurityClient swcclient
= {
2696 .accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&accessGroup
, 1, &kCFTypeArrayCallBacks
),
2697 .allowSystemKeychain
= false,
2698 .allowSyncBubbleKeychain
= false,
2699 .isNetworkExtension
= false,
2700 .musr
= client
->musr
,
2703 // On input, the query dictionary contains optional fqdn and account entries.
2704 fqdn
= CFDictionaryGetValue(query
, kSecAttrServer
);
2705 account
= CFDictionaryGetValue(query
, kSecAttrAccount
);
2707 // Check autofill enabled status
2708 if (!swca_autofill_enabled(clientAuditToken
)) {
2709 SecError(errSecBadReq
, error
, CFSTR("Autofill is not enabled in Safari settings"));
2713 // Check fqdn; if NULL, add domains from caller's entitlement.
2715 CFArrayAppendValue(fqdns
, fqdn
);
2718 CFIndex idx
, count
= CFArrayGetCount(domains
);
2719 for (idx
=0; idx
< count
; idx
++) {
2720 CFStringRef str
= (CFStringRef
) CFArrayGetValueAtIndex(domains
, idx
);
2721 // Parse the entry for our service label prefix
2722 if (str
&& CFStringHasPrefix(str
, kSecSharedWebCredentialsService
)) {
2723 CFIndex prefix_len
= CFStringGetLength(kSecSharedWebCredentialsService
)+1;
2724 CFIndex substr_len
= CFStringGetLength(str
) - prefix_len
;
2725 CFRange range
= { prefix_len
, substr_len
};
2726 fqdn
= CFStringCreateWithSubstring(kCFAllocatorDefault
, str
, range
);
2728 CFArrayAppendValue(fqdns
, fqdn
);
2736 count
= CFArrayGetCount(fqdns
);
2738 SecError(errSecParam
, error
, CFSTR("No domain provided"));
2742 // Aggregate search results for each domain
2743 for (idx
= 0; idx
< count
; idx
++) {
2744 CFMutableArrayRef items
= NULL
;
2745 CFMutableDictionaryRef attrs
= NULL
;
2746 fqdn
= (CFStringRef
) CFArrayGetValueAtIndex(fqdns
, idx
);
2750 // Parse the fqdn for a possible port specifier.
2752 CFStringRef urlStr
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%@%@"), kSecSharedCredentialUrlScheme
, fqdn
);
2754 CFURLRef url
= CFURLCreateWithString(kCFAllocatorDefault
, urlStr
, nil
);
2756 CFStringRef hostname
= CFURLCopyHostName(url
);
2758 CFReleaseSafe(fqdn
);
2760 port
= CFURLGetPortNumber(url
);
2764 CFReleaseSafe(urlStr
);
2768 #if TARGET_OS_SIMULATOR
2769 secerror("app/site association entitlements not checked in Simulator");
2771 OSStatus status
= errSecMissingEntitlement
;
2773 SecError(status
, error
, CFSTR("Missing application-identifier entitlement"));
2774 CFReleaseSafe(fqdn
);
2777 // validate that fqdn is part of caller's entitlement
2778 if (_SecEntitlementContainsDomainForService(domains
, fqdn
, kSecSharedWebCredentialsService
)) {
2779 status
= errSecSuccess
;
2781 if (errSecSuccess
!= status
) {
2782 CFStringRef msg
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
,
2783 CFSTR("%@ not found in %@ entitlement"), fqdn
, kSecEntitlementAssociatedDomains
);
2785 msg
= CFRetain(CFSTR("Requested domain not found in entitlement"));
2787 SecError(status
, error
, CFSTR("%@"), msg
);
2789 CFReleaseSafe(fqdn
);
2794 attrs
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2796 SecError(errSecAllocate
, error
, CFSTR("Unable to create query dictionary"));
2797 CFReleaseSafe(fqdn
);
2800 CFDictionaryAddValue(attrs
, kSecClass
, kSecClassInternetPassword
);
2801 CFDictionaryAddValue(attrs
, kSecAttrAccessGroup
, kSecSafariAccessGroup
);
2802 CFDictionaryAddValue(attrs
, kSecAttrProtocol
, kSecAttrProtocolHTTPS
);
2803 CFDictionaryAddValue(attrs
, kSecAttrAuthenticationType
, kSecAttrAuthenticationTypeHTMLForm
);
2804 CFDictionaryAddValue(attrs
, kSecAttrServer
, fqdn
);
2806 CFDictionaryAddValue(attrs
, kSecAttrAccount
, account
);
2808 if (port
< -1 || port
> 0) {
2809 SInt16 portValueShort
= (port
& 0xFFFF);
2810 CFNumberRef portNumber
= CFNumberCreate(NULL
, kCFNumberSInt16Type
, &portValueShort
);
2811 CFDictionaryAddValue(attrs
, kSecAttrPort
, portNumber
);
2812 CFReleaseSafe(portNumber
);
2814 CFDictionaryAddValue(attrs
, kSecAttrSynchronizable
, kCFBooleanTrue
);
2815 CFDictionaryAddValue(attrs
, kSecMatchLimit
, kSecMatchLimitAll
);
2816 CFDictionaryAddValue(attrs
, kSecReturnAttributes
, kCFBooleanTrue
);
2817 CFDictionaryAddValue(attrs
, kSecReturnData
, kCFBooleanTrue
);
2819 ok
= _SecItemCopyMatching(attrs
, &swcclient
, (CFTypeRef
*)&items
, error
);
2821 // ignore interim error since we have multiple domains to search
2822 CFReleaseNull(*error
);
2824 if (ok
&& items
&& CFGetTypeID(items
) == CFArrayGetTypeID()) {
2825 #if TARGET_OS_SIMULATOR
2826 secerror("Ignoring app/site approval state in the Simulator.");
2827 bool approved
= true;
2829 // get approval status for this app/domain pair
2830 SWCFlags flags
= _SecAppDomainApprovalStatus(appID
, fqdn
, error
);
2832 // ignore interim error since we have multiple domains to check
2833 CFReleaseNull(*error
);
2835 bool approved
= (flags
& kSWCFlag_SiteApproved
);
2838 CFArrayAppendArray(foundItems
, items
, CFRangeMake(0, CFArrayGetCount(items
)));
2841 CFReleaseSafe(items
);
2842 CFReleaseSafe(attrs
);
2843 CFReleaseSafe(fqdn
);
2846 // If matching credentials are found, the credentials provided to the completionHandler
2847 // will be a CFArrayRef containing CFDictionaryRef entries. Each dictionary entry will
2848 // contain the following pairs (see Security/SecItem.h):
2849 // key: kSecAttrServer value: CFStringRef (the website)
2850 // key: kSecAttrAccount value: CFStringRef (the account)
2851 // key: kSecSharedPassword value: CFStringRef (the password)
2853 // key: kSecAttrPort value: CFNumberRef (the port number, if non-standard for https)
2855 count
= CFArrayGetCount(foundItems
);
2856 for (idx
= 0; idx
< count
; idx
++) {
2857 CFDictionaryRef dict
= (CFDictionaryRef
) CFArrayGetValueAtIndex(foundItems
, idx
);
2858 CFMutableDictionaryRef newdict
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2859 if (newdict
&& dict
&& CFGetTypeID(dict
) == CFDictionaryGetTypeID()) {
2860 CFStringRef srvr
= CFDictionaryGetValue(dict
, kSecAttrServer
);
2861 CFStringRef acct
= CFDictionaryGetValue(dict
, kSecAttrAccount
);
2862 CFNumberRef pnum
= CFDictionaryGetValue(dict
, kSecAttrPort
);
2863 CFStringRef icmt
= CFDictionaryGetValue(dict
, kSecAttrComment
);
2864 CFDataRef data
= CFDictionaryGetValue(dict
, kSecValueData
);
2866 CFDictionaryAddValue(newdict
, kSecAttrServer
, srvr
);
2869 CFDictionaryAddValue(newdict
, kSecAttrAccount
, acct
);
2873 if (CFNumberGetValue(pnum
, kCFNumberSInt16Type
, &pval
) &&
2874 (pval
< -1 || pval
> 0)) {
2875 CFDictionaryAddValue(newdict
, kSecAttrPort
, pnum
);
2879 CFStringRef password
= CFStringCreateFromExternalRepresentation(kCFAllocatorDefault
, data
, kCFStringEncodingUTF8
);
2881 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH && !TARGET_OS_TV
2882 CFDictionaryAddValue(newdict
, kSecSharedPassword
, password
);
2884 CFDictionaryAddValue(newdict
, CFSTR("spwd"), password
);
2886 CFReleaseSafe(password
);
2890 if (acct
&& CFEqual(acct
, kSecSafariPasswordsNotSaved
)) {
2891 // Do not add to credentials list!
2892 secwarning("copySWC: Skipping \"%@\" item", kSecSafariPasswordsNotSaved
);
2893 } else if (icmt
&& CFEqual(icmt
, kSecSafariDefaultComment
)) {
2894 CFArrayInsertValueAtIndex(credentials
, 0, newdict
);
2896 CFArrayAppendValue(credentials
, newdict
);
2899 CFReleaseSafe(newdict
);
2902 count
= CFArrayGetCount(credentials
);
2905 // create a new array of dictionaries (without the actual password) for picker UI
2906 CFMutableArrayRef items
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
2907 for (idx
= 0; idx
< count
; idx
++) {
2908 CFDictionaryRef dict
= (CFDictionaryRef
) CFArrayGetValueAtIndex(credentials
, idx
);
2909 CFMutableDictionaryRef newdict
= CFDictionaryCreateMutableCopy(kCFAllocatorDefault
, 0, dict
);
2910 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH && !TARGET_OS_TV
2911 CFDictionaryRemoveValue(newdict
, kSecSharedPassword
);
2913 CFDictionaryRemoveValue(newdict
, CFSTR("spwd"));
2915 CFArrayAppendValue(items
, newdict
);
2916 CFReleaseSafe(newdict
);
2919 // prompt user to select one of the dictionary items
2920 CFDictionaryRef selected
= swca_copy_selected_dictionary(swca_select_request_id
,
2921 clientAuditToken
, items
, error
);
2923 // find the matching item in our credentials array
2924 CFStringRef srvr
= CFDictionaryGetValue(selected
, kSecAttrServer
);
2925 CFStringRef acct
= CFDictionaryGetValue(selected
, kSecAttrAccount
);
2926 CFNumberRef pnum
= CFDictionaryGetValue(selected
, kSecAttrPort
);
2927 for (idx
= 0; idx
< count
; idx
++) {
2928 CFDictionaryRef dict
= (CFDictionaryRef
) CFArrayGetValueAtIndex(credentials
, idx
);
2929 CFStringRef srvr1
= CFDictionaryGetValue(dict
, kSecAttrServer
);
2930 CFStringRef acct1
= CFDictionaryGetValue(dict
, kSecAttrAccount
);
2931 CFNumberRef pnum1
= CFDictionaryGetValue(dict
, kSecAttrPort
);
2933 if (!srvr
|| !srvr1
|| !CFEqual(srvr
, srvr1
)) continue;
2934 if (!acct
|| !acct1
|| !CFEqual(acct
, acct1
)) continue;
2935 if ((pnum
&& pnum1
) && !CFEqual(pnum
, pnum1
)) continue;
2938 CFReleaseSafe(selected
);
2945 CFReleaseSafe(items
);
2946 CFArrayRemoveAllValues(credentials
);
2947 if (selected
&& ok
) {
2948 #if TARGET_OS_IOS && !TARGET_OS_BRIDGE && !TARGET_OS_SIMULATOR
2949 fqdn
= CFDictionaryGetValue(selected
, kSecAttrServer
);
2951 CFArrayAppendValue(credentials
, selected
);
2955 #if TARGET_OS_IOS && !TARGET_OS_BRIDGE && !TARGET_OS_SIMULATOR
2956 // register confirmation with database
2957 CFRetainSafe(appID
);
2959 if (0 != SWCSetServiceFlags(kSecSharedWebCredentialsService
,
2960 appID
, fqdn
, kSWCFlag_ExternalMask
, kSWCFlag_UserApproved
,
2961 ^void(OSStatus inStatus
, SWCFlags inNewFlags
){
2962 CFReleaseSafe(appID
);
2963 CFReleaseSafe(fqdn
);
2966 // we didn't queue the block
2967 CFReleaseSafe(appID
);
2968 CFReleaseSafe(fqdn
);
2972 CFReleaseSafe(selected
);
2974 else if (NULL
== *error
) {
2975 // found no items, and we haven't already filled in the error
2976 SecError(errSecItemNotFound
, error
, CFSTR("no matching items found"));
2981 CFArrayRemoveAllValues(credentials
);
2982 CFReleaseNull(credentials
);
2984 CFReleaseSafe(foundItems
);
2985 *result
= credentials
;
2986 CFReleaseSafe(swcclient
.accessGroups
);
2987 CFReleaseSafe(fqdns
);
2992 #endif /* SHAREDWEBCREDENTIALS */
2996 // MARK: Keychain backup
2998 CF_RETURNS_RETAINED CFDataRef
2999 _SecServerKeychainCreateBackup(SecurityClient
*client
, CFDataRef keybag
, CFDataRef passcode
, bool emcs
, CFErrorRef
*error
) {
3000 __block CFDataRef backup
= NULL
;
3001 kc_with_dbt(false, error
, ^bool (SecDbConnectionRef dbt
) {
3003 LKABackupReportStart(!!keybag
, !!passcode
, emcs
);
3005 return kc_transaction_type(dbt
, kSecDbNormalTransactionType
, error
, ^bool{
3006 secnotice("SecServerKeychainCreateBackup", "Performing backup from %s keybag%s", keybag
? "provided" : "device", emcs
? ", EMCS mode" : "");
3008 if (keybag
== NULL
&& passcode
== NULL
) {
3010 backup
= SecServerExportBackupableKeychain(dbt
, client
, KEYBAG_DEVICE
, backup_keybag_handle
, error
);
3011 #else /* !USE_KEYSTORE */
3013 SecError(errSecParam
, error
, CFSTR("Why are you doing this?"));
3015 #endif /* USE_KEYSTORE */
3017 backup
= SecServerKeychainCreateBackup(dbt
, client
, keybag
, passcode
, emcs
, error
);
3019 return (backup
!= NULL
);
3023 secnotice("SecServerKeychainCreateBackup", "Backup result: %s (%@)", backup
? "success" : "fail", error
? *error
: NULL
);
3024 LKABackupReportEnd(!!backup
, error
? *error
: NULL
);
3030 _SecServerKeychainRestore(CFDataRef backup
, SecurityClient
*client
, CFDataRef keybag
, CFDataRef passcode
, CFErrorRef
*error
) {
3031 if (backup
== NULL
|| keybag
== NULL
)
3032 return SecError(errSecParam
, error
, CFSTR("backup or keybag missing"));
3034 __block
bool ok
= true;
3035 ok
&= kc_with_dbt(true, error
, ^bool (SecDbConnectionRef dbconn
) {
3036 return SecServerKeychainRestore(dbconn
, client
, backup
, keybag
, passcode
, error
);
3040 SecKeychainChanged();
3047 _SecServerBackupCopyUUID(CFDataRef data
, CFErrorRef
*error
)
3049 CFStringRef uuid
= NULL
;
3050 CFDictionaryRef backup
;
3052 backup
= CFPropertyListCreateWithData(kCFAllocatorDefault
, data
,
3053 kCFPropertyListImmutable
, NULL
,
3055 if (isDictionary(backup
)) {
3056 uuid
= SecServerBackupGetKeybagUUID(backup
, error
);
3060 CFReleaseNull(backup
);
3068 // MARK: SecItemDataSource
3070 #if SECUREOBJECTSYNC
3072 // Make sure to call this before any writes to the keychain, so that we fire
3073 // up the engines to monitor manifest changes.
3074 SOSDataSourceFactoryRef
SecItemDataSourceFactoryGetDefault(void) {
3075 return SecItemDataSourceFactoryGetShared(kc_dbhandle(NULL
));
3078 /* AUDIT[securityd]:
3079 args_in (ok) is a caller provided, CFDictionaryRef.
3082 CF_RETURNS_RETAINED CFArrayRef
3083 _SecServerKeychainSyncUpdateMessage(CFDictionaryRef updates
, CFErrorRef
*error
) {
3084 // This never fails, trust us!
3085 return SOSCCHandleUpdateMessage(updates
);
3089 // Truthiness in the cloud backup/restore support.
3092 static CFDictionaryRef
3093 _SecServerCopyTruthInTheCloud(CFDataRef keybag
, CFDataRef password
,
3094 CFDictionaryRef backup
, CFErrorRef
*error
)
3096 SOSManifestRef mold
= NULL
, mnow
= NULL
, mdelete
= NULL
, madd
= NULL
;
3097 __block CFMutableDictionaryRef backup_new
= NULL
;
3098 keybag_handle_t bag_handle
;
3099 if (!ks_open_keybag(keybag
, password
, &bag_handle
, error
))
3102 // We need to have a datasource singleton for protection domain
3103 // kSecAttrAccessibleWhenUnlocked and keep a single shared engine
3104 // instance around which we create in the datasource constructor as well.
3105 SOSDataSourceFactoryRef dsf
= SecItemDataSourceFactoryGetDefault();
3106 SOSDataSourceRef ds
= SOSDataSourceFactoryCreateDataSource(dsf
, kSecAttrAccessibleWhenUnlocked
, error
);
3108 backup_new
= backup
? CFDictionaryCreateMutableCopy(kCFAllocatorDefault
, 0, backup
) : CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
3109 mold
= SOSCreateManifestWithBackup(backup
, error
);
3110 SOSEngineRef engine
= SOSDataSourceGetSharedEngine(ds
, error
);
3111 mnow
= SOSEngineCopyManifest(engine
, NULL
);
3113 mnow
= SOSDataSourceCopyManifestWithViewNameSet(ds
, SOSViewsGetV0ViewSet(), error
);
3116 CFReleaseNull(backup_new
);
3117 secerror("failed to obtain manifest for keychain: %@", error
? *error
: NULL
);
3119 SOSManifestDiff(mold
, mnow
, &mdelete
, &madd
, error
);
3122 // Delete everything from the new_backup that is no longer in the datasource according to the datasources manifest.
3123 SOSManifestForEach(mdelete
, ^(CFDataRef digest_data
, bool *stop
) {
3124 CFStringRef deleted_item_key
= CFDataCopyHexString(digest_data
);
3125 CFDictionaryRemoveValue(backup_new
, deleted_item_key
);
3126 CFRelease(deleted_item_key
);
3129 CFMutableArrayRef changes
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
3130 SOSDataSourceForEachObject(ds
, NULL
, madd
, error
, ^void(CFDataRef digest
, SOSObjectRef object
, bool *stop
) {
3131 CFErrorRef localError
= NULL
;
3132 CFDataRef digest_data
= NULL
;
3133 CFTypeRef value
= NULL
;
3135 // Key in our manifest can't be found in db, remove it from our manifest
3136 SOSChangesAppendDelete(changes
, digest
);
3137 } else if (!(digest_data
= SOSObjectCopyDigest(ds
, object
, &localError
))
3138 || !(value
= SOSObjectCopyBackup(ds
, object
, bag_handle
, &localError
))) {
3139 if (SecErrorGetOSStatus(localError
) == errSecDecode
) {
3140 // Ignore decode errors, pretend the objects aren't there
3141 CFRelease(localError
);
3142 // Object undecodable, remove it from our manifest
3143 SOSChangesAppendDelete(changes
, digest
);
3145 // Stop iterating and propagate out all other errors.
3147 *error
= localError
;
3148 CFReleaseNull(backup_new
);
3151 // TODO: Should we skip tombstones here?
3152 CFStringRef key
= CFDataCopyHexString(digest_data
);
3153 CFDictionarySetValue(backup_new
, key
, value
);
3156 CFReleaseSafe(digest_data
);
3157 CFReleaseSafe(value
);
3158 }) || CFReleaseNull(backup_new
);
3160 if (CFArrayGetCount(changes
)) {
3161 if (!SOSEngineUpdateChanges(engine
, kSOSDataSourceSOSTransaction
, changes
, error
)) {
3162 CFReleaseNull(backup_new
);
3165 CFReleaseSafe(changes
);
3167 SOSDataSourceRelease(ds
, error
) || CFReleaseNull(backup_new
);
3170 CFReleaseSafe(mold
);
3171 CFReleaseSafe(mnow
);
3172 CFReleaseSafe(madd
);
3173 CFReleaseSafe(mdelete
);
3174 ks_close_keybag(bag_handle
, error
) || CFReleaseNull(backup_new
);
3180 _SecServerRestoreTruthInTheCloud(CFDataRef keybag
, CFDataRef password
, CFDictionaryRef backup_in
, CFErrorRef
*error
) {
3181 __block
bool ok
= true;
3182 keybag_handle_t bag_handle
;
3183 if (!ks_open_keybag(keybag
, password
, &bag_handle
, error
))
3186 SOSManifestRef mbackup
= SOSCreateManifestWithBackup(backup_in
, error
);
3188 SOSDataSourceFactoryRef dsf
= SecItemDataSourceFactoryGetDefault();
3189 SOSDataSourceRef ds
= SOSDataSourceFactoryCreateDataSource(dsf
, kSecAttrAccessibleWhenUnlocked
, error
);
3190 ok
&= ds
&& SOSDataSourceWith(ds
, error
, ^(SOSTransactionRef txn
, bool *commit
) {
3191 SOSManifestRef mnow
= SOSDataSourceCopyManifestWithViewNameSet(ds
, SOSViewsGetV0BackupViewSet(), error
);
3192 SOSManifestRef mdelete
= NULL
, madd
= NULL
;
3193 SOSManifestDiff(mnow
, mbackup
, &mdelete
, &madd
, error
);
3195 // Don't delete everything in datasource not in backup.
3197 // Add items from the backup
3198 SOSManifestForEach(madd
, ^void(CFDataRef e
, bool *stop
) {
3199 CFDictionaryRef item
= NULL
;
3200 CFStringRef sha1
= CFDataCopyHexString(e
);
3202 item
= CFDictionaryGetValue(backup_in
, sha1
);
3206 CFErrorRef localError
= NULL
;
3208 if (!SOSObjectRestoreObject(ds
, txn
, bag_handle
, item
, &localError
)) {
3209 OSStatus status
= SecErrorGetOSStatus(localError
);
3210 if (status
== errSecDuplicateItem
) {
3211 // Log and ignore duplicate item errors during restore
3212 secnotice("titc", "restore %@ not replacing existing item", item
);
3213 } else if (status
== errSecDecode
) {
3214 // Log and ignore corrupted item errors during restore
3215 secnotice("titc", "restore %@ skipping corrupted item %@", item
, localError
);
3217 if (status
== errSecInteractionNotAllowed
)
3219 // Propagate the first other error upwards (causing the restore to fail).
3220 secerror("restore %@ failed %@", item
, localError
);
3222 if (error
&& !*error
) {
3223 *error
= localError
;
3227 CFReleaseSafe(localError
);
3231 ok
&= SOSDataSourceRelease(ds
, error
);
3232 CFReleaseNull(mdelete
);
3233 CFReleaseNull(madd
);
3234 CFReleaseNull(mnow
);
3239 ok
&= ks_close_keybag(bag_handle
, error
);
3245 CF_RETURNS_RETAINED CFDictionaryRef
3246 _SecServerBackupSyncable(CFDictionaryRef backup
, CFDataRef keybag
, CFDataRef password
, CFErrorRef
*error
) {
3247 require_action_quiet(isData(keybag
), errOut
, SecError(errSecParam
, error
, CFSTR("keybag %@ not a data"), keybag
));
3248 require_action_quiet(!backup
|| isDictionary(backup
), errOut
, SecError(errSecParam
, error
, CFSTR("backup %@ not a dictionary"), backup
));
3249 require_action_quiet(!password
|| isData(password
), errOut
, SecError(errSecParam
, error
, CFSTR("password %@ not a data"), password
));
3251 return _SecServerCopyTruthInTheCloud(keybag
, password
, backup
, error
);
3258 _SecServerRestoreSyncable(CFDictionaryRef backup
, CFDataRef keybag
, CFDataRef password
, CFErrorRef
*error
) {
3260 require_action_quiet(isData(keybag
), errOut
, ok
= SecError(errSecParam
, error
, CFSTR("keybag %@ not a data"), keybag
));
3261 require_action_quiet(isDictionary(backup
), errOut
, ok
= SecError(errSecParam
, error
, CFSTR("backup %@ not a dictionary"), backup
));
3264 require_action_quiet(isData(password
), errOut
, ok
= SecError(errSecParam
, error
, CFSTR("password not a data")));
3267 ok
= _SecServerRestoreTruthInTheCloud(keybag
, password
, backup
, error
);
3272 #endif /* SECUREOBJECTSYNC */
3274 bool _SecServerRollKeysGlue(bool force
, CFErrorRef
*error
) {
3275 return _SecServerRollKeys(force
, NULL
, error
);
3279 bool _SecServerRollKeys(bool force
, SecurityClient
*client
, CFErrorRef
*error
) {
3281 uint32_t keystore_generation_status
= 0;
3282 if (aks_generation(KEYBAG_DEVICE
, generation_noop
, &keystore_generation_status
))
3284 uint32_t current_generation
= keystore_generation_status
& generation_current
;
3286 return kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
3287 bool up_to_date
= s3dl_dbt_keys_current(dbt
, current_generation
, NULL
);
3289 if (force
&& !up_to_date
) {
3290 up_to_date
= s3dl_dbt_update_keys(dbt
, client
, error
);
3292 secerror("Completed roll keys.");
3293 up_to_date
= s3dl_dbt_keys_current(dbt
, current_generation
, NULL
);
3296 secerror("Failed to roll keys.");
3306 InitialSyncItems(CFMutableArrayRef items
, bool limitToCurrent
, CFStringRef agrp
, CFStringRef svce
, const SecDbClass
*qclass
, CFErrorRef
*error
)
3308 bool result
= false;
3311 q
= query_create(qclass
, NULL
, NULL
, error
);
3314 q
->q_return_type
= kSecReturnDataMask
| kSecReturnAttributesMask
;
3315 q
->q_limit
= kSecMatchUnlimited
;
3316 q
->q_keybag
= KEYBAG_DEVICE
;
3318 query_add_attribute(kSecAttrAccessGroup
, agrp
, q
);
3319 query_add_attribute(kSecAttrSynchronizable
, kCFBooleanTrue
, q
);
3320 query_add_attribute(kSecAttrTombstone
, kCFBooleanFalse
, q
);
3322 query_add_attribute(kSecAttrService
, svce
, q
);
3324 result
= kc_with_dbt(false, error
, ^(SecDbConnectionRef dbt
) {
3325 return kc_transaction(dbt
, error
, ^{
3326 CFErrorRef error2
= NULL
;
3328 SecDbItemSelect(q
, dbt
, &error2
, NULL
, ^bool(const SecDbAttr
*attr
) {
3329 return CFDictionaryGetValue(q
->q_item
, attr
->name
);
3330 }, NULL
, NULL
, ^(SecDbItemRef item
, bool *stop
) {
3331 CFErrorRef error3
= NULL
;
3332 secinfo("InitialSyncItems", "Copy item");
3334 CFMutableDictionaryRef attrs
= SecDbItemCopyPListWithMask(item
, kSecDbSyncFlag
, &error3
);
3337 CFStringRef itemvwht
= CFDictionaryGetValue(attrs
, kSecAttrSyncViewHint
);
3339 * Saying its a SOS viewhint is really not the right answer post Triangle
3341 if (isString(itemvwht
) && !SOSViewInSOSSystem(itemvwht
)) {
3345 * Here we encode how PCS stores identities so that we only copy the
3346 * current identites for performance reasons.
3348 if (limitToCurrent
) {
3349 enum { PCS_CURRENT_IDENTITY_OFFSET
= 0x10000 };
3352 CFNumberRef type
= CFDictionaryGetValue(attrs
, kSecAttrType
);
3353 if (!isNumber(type
)) {
3354 // still allow this case since its not a service identity ??
3355 } else if (!CFNumberGetValue(type
, kCFNumberSInt32Type
, &s32
)) {
3357 } else if ((s32
& PCS_CURRENT_IDENTITY_OFFSET
) == 0) {
3362 CFDictionaryAddValue(attrs
, kSecClass
, SecDbItemGetClass(item
)->name
);
3363 CFArrayAppendValue(items
, attrs
);
3366 CFReleaseNull(attrs
);
3368 CFReleaseNull(error3
);
3370 CFReleaseNull(error2
);
3378 query_destroy(q
, NULL
);
3383 _SecServerCopyInitialSyncCredentials(uint32_t flags
, CFErrorRef
*error
)
3385 CFMutableArrayRef items
= CFArrayCreateMutableForCFTypes(NULL
);
3387 if (flags
& SecServerInitialSyncCredentialFlagTLK
) {
3388 require_action(InitialSyncItems(items
, false, CFSTR("com.apple.security.ckks"), NULL
, inet_class(), error
), fail
,
3389 secerror("failed to collect CKKS-inet keys: %@", error
? *error
: NULL
));
3391 if (flags
& SecServerInitialSyncCredentialFlagPCS
) {
3392 bool onlyCurrent
= !(flags
& SecServerInitialSyncCredentialFlagPCSNonCurrent
);
3394 require_action(InitialSyncItems(items
, false, CFSTR("com.apple.ProtectedCloudStorage"), NULL
, genp_class(), error
), fail
,
3395 secerror("failed to collect PCS-genp keys: %@", error
? *error
: NULL
));
3396 require_action(InitialSyncItems(items
, onlyCurrent
, CFSTR("com.apple.ProtectedCloudStorage"), NULL
, inet_class(), error
), fail
,
3397 secerror("failed to collect PCS-inet keys: %@", error
? *error
: NULL
));
3399 if (flags
& SecServerInitialSyncCredentialFlagBluetoothMigration
) {
3400 require_action(InitialSyncItems(items
, false, CFSTR("com.apple.nanoregistry.migration"), NULL
, genp_class(), error
), fail
,
3401 secerror("failed to collect com.apple.nanoregistry.migration-genp item: %@", error
? *error
: NULL
));
3402 require_action(InitialSyncItems(items
, false, CFSTR("com.apple.nanoregistry.migration2"), NULL
, genp_class(), error
), fail
,
3403 secerror("failed to collect com.apple.nanoregistry.migration2-genp item: %@", error
? *error
: NULL
));
3404 require_action(InitialSyncItems(items
, false, CFSTR("com.apple.bluetooth"), CFSTR("BluetoothLESync"), genp_class(), error
), fail
,
3405 secerror("failed to collect com.apple.bluetooth-genp item: %@", error
? *error
: NULL
));
3414 _SecServerImportInitialSyncCredentials(CFArrayRef array
, CFErrorRef
*error
)
3416 return kc_with_dbt(true, error
, ^bool(SecDbConnectionRef dbt
) {
3417 return kc_transaction(dbt
, error
, ^bool(void){
3418 CFIndex n
, count
= CFArrayGetCount(array
);
3420 secinfo("ImportInitialSyncItems", "Importing %d items", (int)count
);
3422 for (n
= 0; n
< count
; n
++) {
3423 CFErrorRef cferror
= NULL
;
3425 CFDictionaryRef item
= CFArrayGetValueAtIndex(array
, n
);
3426 if (!isDictionary(item
))
3429 CFStringRef className
= CFDictionaryGetValue(item
, kSecClass
);
3430 if (className
== NULL
) {
3431 secinfo("ImportInitialSyncItems", "Item w/o class");
3435 const SecDbClass
*cls
= kc_class_with_name(className
);
3437 secinfo("ImportInitialSyncItems", "Item with unknown class: %@", className
);
3441 SecDbItemRef dbi
= SecDbItemCreateWithAttributes(NULL
, cls
, item
, KEYBAG_DEVICE
, &cferror
);
3443 secinfo("ImportInitialSyncItems", "Item creation failed with: %@", cferror
);
3444 CFReleaseNull(cferror
);
3448 if (!SecDbItemSetSyncable(dbi
, true, &cferror
)) {
3449 secinfo("ImportInitialSyncItems", "Failed to set sync=1: %@ for item %@", cferror
, dbi
);
3450 CFReleaseNull(cferror
);
3455 if (!SecDbItemInsert(dbi
, dbt
, &cferror
)) {
3456 secinfo("ImportInitialSyncItems", "Item store failed with: %@: %@", cferror
, dbi
);
3457 CFReleaseNull(cferror
);
3471 * Sync bubble migration code
3474 struct SyncBubbleRule
{
3475 CFStringRef attribute
;
3480 TransmogrifyItemsToSyncBubble(SecurityClient
*client
, uid_t uid
,
3483 const SecDbClass
*qclass
,
3484 struct SyncBubbleRule
*items
, CFIndex nItems
,
3487 CFMutableDictionaryRef updateAttributes
= NULL
;
3488 CFDataRef syncBubbleView
= NULL
;
3489 CFDataRef activeUserView
= NULL
;
3494 syncBubbleView
= SecMUSRCreateSyncBubbleUserUUID(uid
);
3495 require(syncBubbleView
, fail
);
3497 activeUserView
= SecMUSRCreateActiveUserUUID(uid
);
3498 require(activeUserView
, fail
);
3501 if ((onlyDelete
&& !copyToo
) || !onlyDelete
) {
3504 * Clean out items first
3507 secnotice("syncbubble", "cleaning out old items");
3509 q
= query_create(qclass
, NULL
, NULL
, error
);
3512 q
->q_limit
= kSecMatchUnlimited
;
3513 q
->q_keybag
= device_keybag_handle
;
3515 for (n
= 0; n
< nItems
; n
++) {
3516 query_add_attribute(items
[n
].attribute
, items
[n
].value
, q
);
3518 q
->q_musrView
= CFRetain(syncBubbleView
);
3519 require(q
->q_musrView
, fail
);
3521 kc_with_dbt(false, error
, ^(SecDbConnectionRef dbt
) {
3522 return kc_transaction(dbt
, error
, ^{
3523 return s3dl_query_delete(dbt
, q
, NULL
, error
);
3527 query_destroy(q
, NULL
);
3532 if (onlyDelete
|| !copyToo
) {
3533 secnotice("syncbubble", "skip migration of items");
3536 * Copy over items from EMCS to sync bubble
3539 secnotice("syncbubble", "migrating sync bubble items");
3541 q
= query_create(qclass
, NULL
, NULL
, error
);
3544 q
->q_return_type
= kSecReturnDataMask
| kSecReturnAttributesMask
;
3545 q
->q_limit
= kSecMatchUnlimited
;
3546 q
->q_keybag
= device_keybag_handle
; /* XXX change to session key bag when it exists */
3548 for (n
= 0; n
< nItems
; n
++) {
3549 query_add_or_attribute(items
[n
].attribute
, items
[n
].value
, q
);
3551 query_add_or_attribute(CFSTR("musr"), activeUserView
, q
);
3552 q
->q_musrView
= CFRetain(activeUserView
);
3554 updateAttributes
= CFDictionaryCreateMutableForCFTypes(NULL
);
3555 require(updateAttributes
, fail
);
3557 CFDictionarySetValue(updateAttributes
, CFSTR("musr"), syncBubbleView
); /* XXX should use kSecAttrMultiUser */
3560 kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
3561 return kc_transaction(dbt
, error
, ^{
3562 CFErrorRef error2
= NULL
;
3564 SecDbItemSelect(q
, dbt
, &error2
, NULL
, ^bool(const SecDbAttr
*attr
) {
3565 return CFDictionaryGetValue(q
->q_item
, attr
->name
);
3566 }, NULL
, NULL
, ^(SecDbItemRef item
, bool *stop
) {
3567 CFErrorRef error3
= NULL
;
3568 secinfo("syncbubble", "migrating item");
3570 SecDbItemRef new_item
= SecDbItemCopyWithUpdates(item
, updateAttributes
, NULL
);
3571 if (new_item
== NULL
)
3574 SecDbItemClearRowId(new_item
, NULL
);
3576 if (!SecDbItemSetKeybag(new_item
, device_keybag_handle
, NULL
)) {
3577 CFRelease(new_item
);
3581 if (!SecDbItemInsert(new_item
, dbt
, &error3
)) {
3582 secnotice("syncbubble", "migration failed with %@ for item %@", error3
, new_item
);
3584 CFRelease(new_item
);
3585 CFReleaseNull(error3
);
3587 CFReleaseNull(error2
);
3596 CFReleaseNull(syncBubbleView
);
3597 CFReleaseNull(activeUserView
);
3598 CFReleaseNull(updateAttributes
);
3600 query_destroy(q
, NULL
);
3605 static struct SyncBubbleRule PCSItems
[] = {
3607 .attribute
= CFSTR("agrp"),
3608 .value
= CFSTR("com.apple.ProtectedCloudStorage"),
3611 static struct SyncBubbleRule NSURLSesssiond
[] = {
3613 .attribute
= CFSTR("agrp"),
3614 .value
= CFSTR("com.apple.nsurlsessiond"),
3617 static struct SyncBubbleRule AccountsdItems
[] = {
3619 .attribute
= CFSTR("svce"),
3620 .value
= CFSTR("com.apple.account.AppleAccount.token"),
3623 .attribute
= CFSTR("svce"),
3624 .value
= CFSTR("com.apple.account.AppleAccount.password"),
3627 .attribute
= CFSTR("svce"),
3628 .value
= CFSTR("com.apple.account.AppleAccount.rpassword"),
3631 .attribute
= CFSTR("svce"),
3632 .value
= CFSTR("com.apple.account.idms.token"),
3635 .attribute
= CFSTR("svce"),
3636 .value
= CFSTR("com.apple.account.idms.continuation-key"),
3639 .attribute
= CFSTR("svce"),
3640 .value
= CFSTR("com.apple.account.CloudKit.token"),
3644 static struct SyncBubbleRule MobileMailItems
[] = {
3646 .attribute
= CFSTR("svce"),
3647 .value
= CFSTR("com.apple.account.IMAP.password"),
3650 .attribute
= CFSTR("svce"),
3651 .value
= CFSTR("com.apple.account.SMTP.password"),
3654 .attribute
= CFSTR("svce"),
3655 .value
= CFSTR("com.apple.account.Exchange.password"),
3658 .attribute
= CFSTR("svce"),
3659 .value
= CFSTR("com.apple.account.Hotmail.password"),
3662 .attribute
= CFSTR("svce"),
3663 .value
= CFSTR("com.apple.account.Google.password"),
3666 .attribute
= CFSTR("svce"),
3667 .value
= CFSTR("com.apple.account.Google.oauth-token"),
3670 .attribute
= CFSTR("svce"),
3671 .value
= CFSTR("com.apple.account.Google.oath-refresh-token"),
3674 .attribute
= CFSTR("svce"),
3675 .value
= CFSTR("com.apple.account.Yahoo.password"),
3678 .attribute
= CFSTR("svce"),
3679 .value
= CFSTR("com.apple.account.Yahoo.oauth-token"),
3682 .attribute
= CFSTR("svce"),
3683 .value
= CFSTR("com.apple.account.Yahoo.oauth-token-nosync"),
3686 .attribute
= CFSTR("svce"),
3687 .value
= CFSTR("com.apple.account.Yahoo.oath-refresh-token"),
3690 .attribute
= CFSTR("svce"),
3691 .value
= CFSTR("com.apple.account.IMAPNotes.password"),
3694 .attribute
= CFSTR("svce"),
3695 .value
= CFSTR("com.apple.account.IMAPMail.password"),
3698 .attribute
= CFSTR("svce"),
3699 .value
= CFSTR("com.apple.account.126.password"),
3702 .attribute
= CFSTR("svce"),
3703 .value
= CFSTR("com.apple.account.163.password"),
3706 .attribute
= CFSTR("svce"),
3707 .value
= CFSTR("com.apple.account.aol.password"),
3712 ArrayContains(CFArrayRef array
, CFStringRef service
)
3714 return CFArrayContainsValue(array
, CFRangeMake(0, CFArrayGetCount(array
)), service
);
3718 _SecServerTransmogrifyToSyncBubble(CFArrayRef services
, uid_t uid
, SecurityClient
*client
, CFErrorRef
*error
)
3720 bool copyCloudAuthToken
= false;
3721 bool copyMobileMail
= false;
3723 bool copyPCS
= false;
3724 bool onlyDelete
= false;
3725 bool copyNSURLSesssion
= false;
3727 if (!client
->inMultiUser
)
3730 secnotice("syncbubble", "migration for uid %d uid for services %@", (int)uid
, services
);
3732 #if TARGET_OS_SIMULATOR
3735 if (uid
!= (uid_t
)client
->activeUser
)
3738 #error "no sync bubble on other platforms"
3742 * First select that services to copy/delete
3745 if (ArrayContains(services
, CFSTR("com.apple.bird.usermanager.sync"))
3746 || ArrayContains(services
, CFSTR("com.apple.cloudphotod.sync"))
3747 || ArrayContains(services
, CFSTR("com.apple.cloudphotod.syncstakeholder"))
3748 || ArrayContains(services
, CFSTR("com.apple.cloudd.usermanager.sync")))
3750 copyCloudAuthToken
= true;
3754 if (ArrayContains(services
, CFSTR("com.apple.nsurlsessiond.usermanager.sync")))
3756 copyCloudAuthToken
= true;
3757 copyNSURLSesssion
= true;
3760 if (ArrayContains(services
, CFSTR("com.apple.syncdefaultsd.usermanager.sync"))) {
3761 copyCloudAuthToken
= true;
3763 if (ArrayContains(services
, CFSTR("com.apple.mailq.sync")) || ArrayContains(services
, CFSTR("com.apple.mailq.sync.xpc"))) {
3764 copyCloudAuthToken
= true;
3765 copyMobileMail
= true;
3770 * The actually copy/delete the items selected
3773 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyPCS
, inet_class(), PCSItems
, sizeof(PCSItems
)/sizeof(PCSItems
[0]), error
);
3775 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyPCS
, genp_class(), PCSItems
, sizeof(PCSItems
)/sizeof(PCSItems
[0]), error
);
3779 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyMobileMail
, genp_class(), MobileMailItems
, sizeof(MobileMailItems
)/sizeof(MobileMailItems
[0]), error
);
3783 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyCloudAuthToken
, genp_class(), AccountsdItems
, sizeof(AccountsdItems
)/sizeof(AccountsdItems
[0]), error
);
3787 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyNSURLSesssion
, inet_class(), NSURLSesssiond
, sizeof(NSURLSesssiond
)/sizeof(NSURLSesssiond
[0]), error
);
3795 * Migrate from user keychain to system keychain when switching to edu mode
3799 _SecServerTransmogrifyToSystemKeychain(SecurityClient
*client
, CFErrorRef
*error
)
3801 __block
bool ok
= true;
3804 * we are not in multi user yet, about to switch, otherwise we would
3805 * check that for client->inMultiuser here
3808 kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
3809 return kc_transaction(dbt
, error
, ^{
3810 CFDataRef systemUUID
= SecMUSRGetSystemKeychainUUID();
3812 const SecDbSchema
*newSchema
= current_schema();
3813 SecDbClass
const *const *kcClass
;
3815 for (kcClass
= newSchema
->classes
; *kcClass
!= NULL
; kcClass
++) {
3816 CFErrorRef localError
= NULL
;
3819 if (!((*kcClass
)->itemclass
)) {
3823 q
= query_create(*kcClass
, SecMUSRGetSingleUserKeychainUUID(), NULL
, error
);
3827 ok
&= SecDbItemSelect(q
, dbt
, error
, ^bool(const SecDbAttr
*attr
) {
3828 return (attr
->flags
& kSecDbInFlag
) != 0;
3829 }, ^bool(const SecDbAttr
*attr
) {
3830 // No filtering please.
3832 }, ^bool(CFMutableStringRef sql
, bool *needWhere
) {
3833 SecDbAppendWhereOrAnd(sql
, needWhere
);
3834 CFStringAppendFormat(sql
, NULL
, CFSTR("musr = ?"));
3836 }, ^bool(sqlite3_stmt
*stmt
, int col
) {
3837 return SecDbBindObject(stmt
, col
++, SecMUSRGetSingleUserKeychainUUID(), error
);
3838 }, ^(SecDbItemRef item
, bool *stop
) {
3839 CFErrorRef itemError
= NULL
;
3841 if (!SecDbItemSetValueWithName(item
, kSecAttrMultiUser
, systemUUID
, &itemError
)) {
3842 secerror("item: %@ update musr to system failed: %@", item
, itemError
);
3847 if (!SecDbItemDoUpdate(item
, item
, dbt
, &itemError
, ^bool (const SecDbAttr
*attr
) {
3848 return attr
->kind
== kSecDbRowIdAttr
;
3850 secerror("item: %@ insert during UPDATE: %@", item
, itemError
);
3856 SecErrorPropagate(itemError
, error
);
3860 query_destroy(q
, &localError
);
3871 * Delete account from local usage
3875 _SecServerDeleteMUSERViews(SecurityClient
*client
, uid_t uid
, CFErrorRef
*error
)
3877 return kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
3878 CFDataRef musrView
= NULL
, syncBubbleView
= NULL
;
3881 syncBubbleView
= SecMUSRCreateSyncBubbleUserUUID(uid
);
3882 require(syncBubbleView
, fail
);
3884 musrView
= SecMUSRCreateActiveUserUUID(uid
);
3885 require(musrView
, fail
);
3887 require(ok
= SecServerDeleteAllForUser(dbt
, syncBubbleView
, false, error
), fail
);
3888 require(ok
= SecServerDeleteAllForUser(dbt
, musrView
, false, error
), fail
);
3891 CFReleaseNull(syncBubbleView
);
3892 CFReleaseNull(musrView
);
3898 #endif /* TARGET_OS_IOS */
3900 CFArrayRef
_SecItemCopyParentCertificates(CFDataRef normalizedIssuer
, CFArrayRef accessGroups
, CFErrorRef
*error
) {
3901 const void *keys
[] = {
3908 kSecClassCertificate
,
3913 CFDictionaryRef query
= CFDictionaryCreate(NULL
, keys
, values
, 4,
3915 CFTypeRef results
= NULL
;
3916 SecurityClient client
= {
3918 .accessGroups
= accessGroups
,
3919 .allowSystemKeychain
= true,
3920 .allowSyncBubbleKeychain
= false,
3921 .isNetworkExtension
= false,
3924 (void)_SecItemCopyMatching(query
, &client
, &results
, error
);
3929 bool _SecItemCertificateExists(CFDataRef normalizedIssuer
, CFDataRef serialNumber
, CFArrayRef accessGroups
, CFErrorRef
*error
) {
3930 const void *keys
[] = {
3934 kSecAttrSerialNumber
3937 kSecClassCertificate
,
3942 SecurityClient client
= {
3944 .accessGroups
= accessGroups
,
3945 .allowSystemKeychain
= true,
3946 .allowSyncBubbleKeychain
= false,
3947 .isNetworkExtension
= false,
3949 CFDictionaryRef query
= CFDictionaryCreate(NULL
, keys
, values
, 4, NULL
, NULL
);
3950 CFTypeRef results
= NULL
;
3951 bool ok
= _SecItemCopyMatching(query
, &client
, &results
, error
);
3952 CFReleaseSafe(query
);
3953 CFReleaseSafe(results
);