2 * Copyright (c) 2006-2017 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
21 * @APPLE_LICENSE_HEADER_END@
25 * SecItemServer.c - CoreFoundation-based constants and functions for
26 access to Security items (certificates, keys, identities, and
30 #include <securityd/SecItemServer.h>
33 #include <securityd/SecItemDataSource.h>
34 #include <securityd/SecItemDb.h>
35 #include <securityd/SecItemSchema.h>
36 #include <utilities/SecDb.h>
37 #include <securityd/SecDbKeychainItem.h>
38 #include <securityd/SOSCloudCircleServer.h>
39 #include <Security/SecBasePriv.h>
40 #include <Security/SecItemPriv.h>
41 #include <Security/SecItemInternal.h>
42 #include <Security/SecureObjectSync/SOSChangeTracker.h>
43 #include <Security/SecureObjectSync/SOSDigestVector.h>
44 #include <Security/SecureObjectSync/SOSEngine.h>
45 #include <Security/SecureObjectSync/SOSViews.h>
46 #include <Security/SecTrustPriv.h>
47 #include <Security/SecTrustInternal.h>
48 #include <Security/SecCertificatePriv.h>
49 #include <Security/SecEntitlements.h>
50 #include <Security/SecSignpost.h>
52 #include <keychain/ckks/CKKS.h>
55 #include <MobileKeyBag/MobileKeyBag.h>
56 #include <Kernel/IOKit/crypto/AppleKeyStoreDefs.h>
58 // TODO: Make this include work on both platforms. rdar://problem/16526848
59 #if TARGET_OS_EMBEDDED
60 #include <Security/SecEntitlements.h>
62 /* defines from <Security/SecEntitlements.h> */
63 #define kSecEntitlementAssociatedDomains CFSTR("com.apple.developer.associated-domains")
64 #define kSecEntitlementPrivateAssociatedDomains CFSTR("com.apple.private.associated-domains")
67 #if TARGET_OS_EMBEDDED
68 #include <utilities/SecADWrapper.h>
73 #include <utilities/array_size.h>
74 #include <utilities/SecFileLocations.h>
75 #include <utilities/SecTrace.h>
76 #include <utilities/SecXPCError.h>
77 #include <utilities/sec_action.h>
78 #include <Security/SecuritydXPC.h>
79 #include "swcagent_client.h"
80 #include "SecPLWrappers.h"
82 #if TARGET_OS_IOS && !TARGET_OS_BRIDGE
83 #include <SharedWebCredentials/SharedWebCredentials.h>
86 #include "Analytics/Clients/LocalKeychainAnalytics.h"
88 /* Changed the name of the keychain changed notification, for testing */
89 static const char *g_keychain_changed_notification
= kSecServerKeychainChangedNotification
;
91 void SecItemServerSetKeychainChangedNotification(const char *notification_name
)
93 g_keychain_changed_notification
= notification_name
;
96 void SecKeychainChanged() {
97 static dispatch_once_t once
;
98 static sec_action_t action
;
100 dispatch_once(&once
, ^{
101 action
= sec_action_create("SecKeychainChanged", 1);
102 sec_action_set_handler(action
, ^{
103 uint32_t result
= notify_post(g_keychain_changed_notification
);
104 if (result
== NOTIFY_STATUS_OK
)
105 secnotice("item", "Sent %s", g_keychain_changed_notification
);
107 secerror("notify_post %s returned: %" PRIu32
, g_keychain_changed_notification
, result
);
111 sec_action_perform(action
);
114 /* Return the current database version in *version. */
115 bool SecKeychainDbGetVersion(SecDbConnectionRef dbt
, int *version
, CFErrorRef
*error
)
117 __block
bool ok
= true;
118 __block CFErrorRef localError
= NULL
;
119 __block
bool found
= false;
122 * First check for the version table itself
125 ok
&= SecDbPrepare(dbt
, CFSTR("SELECT name FROM sqlite_master WHERE type='table' AND name='tversion'"), &localError
, ^(sqlite3_stmt
*stmt
) {
126 ok
= SecDbStep(dbt
, stmt
, NULL
, ^(bool *stop
) {
131 require_action(ok
, out
, SecDbError(SQLITE_CORRUPT
, error
, CFSTR("Failed to read sqlite_master table: %@"), localError
));
133 secnotice("upgr", "no tversion table, will setup a new database: %@", localError
);
139 * Now build up major.minor
142 ok
&= SecDbPrepare(dbt
, CFSTR("SELECT version FROM tversion"), &localError
, ^(sqlite3_stmt
*stmt
) {
143 ok
= SecDbStep(dbt
, stmt
, NULL
, ^(bool *stop
) {
144 *version
= sqlite3_column_int(stmt
, 0);
149 if (ok
&& (*version
& 0xffff) >= 9) {
150 ok
&= SecDbPrepare(dbt
, CFSTR("SELECT minor FROM tversion WHERE version = ?"), &localError
, ^(sqlite3_stmt
*stmt
) {
151 ok
= SecDbBindInt(stmt
, 1, *version
, &localError
) &&
152 SecDbStep(dbt
, stmt
, NULL
, ^(bool *stop
) {
153 int64_t minor
= sqlite3_column_int64(stmt
, 0);
154 *version
|= ((minor
& 0xff) << 8) | ((minor
& 0xff0000) << 8);
161 secnotice("upgr", "database version is: 0x%08x : %d : %@", *version
, ok
, localError
);
162 CFReleaseSafe(localError
);
169 isClassD(SecDbItemRef item
)
171 CFTypeRef accessible
= SecDbItemGetCachedValueWithName(item
, kSecAttrAccessible
);
173 if (CFEqualSafe(accessible
, kSecAttrAccessibleAlways
) || CFEqualSafe(accessible
, kSecAttrAccessibleAlwaysThisDeviceOnly
))
178 #if TARGET_OS_EMBEDDED
181 measureDuration(struct timeval
*start
)
186 gettimeofday(&stop
, NULL
);
188 duration
= (stop
.tv_sec
-start
->tv_sec
) * 1000;
189 duration
+= (stop
.tv_usec
/ 1000) - (start
->tv_usec
/ 1000);
191 return SecBucket2Significant(duration
);
195 measureUpgradePhase1(struct timeval
*start
, bool success
, int64_t itemsMigrated
)
197 int64_t duration
= measureDuration(start
);
200 SecADSetValueForScalarKey(CFSTR("com.apple.keychain.phase1.migrated-items-success"), itemsMigrated
);
201 SecADSetValueForScalarKey(CFSTR("com.apple.keychain.phase1.migrated-time-success"), duration
);
203 SecADSetValueForScalarKey(CFSTR("com.apple.keychain.phase1.migrated-items-fail"), itemsMigrated
);
204 SecADSetValueForScalarKey(CFSTR("com.apple.keychain.phase1.migrated-time-fail"), duration
);
209 measureUpgradePhase2(struct timeval
*start
, int64_t itemsMigrated
)
211 int64_t duration
= measureDuration(start
);
213 SecADSetValueForScalarKey(CFSTR("com.apple.keychain.phase2.migrated-items"), itemsMigrated
);
214 SecADSetValueForScalarKey(CFSTR("com.apple.keychain.phase2.migrated-time"), duration
);
216 #endif /* TARGET_OS_EMBEDDED */
218 static bool DBClassesAreEqual(const SecDbClass
* class1
, const SecDbClass
* class2
)
220 if (CFEqual(class1
->name
, class2
->name
) && class1
->itemclass
== class2
->itemclass
) {
222 const SecDbAttr
* class1Attr
= class1
->attrs
[attrIndex
];
223 const SecDbAttr
* class2Attr
= class2
->attrs
[attrIndex
];
225 while (class1Attr
&& class2Attr
) {
226 if (CFEqual(class1Attr
->name
, class2Attr
->name
) && class1Attr
->kind
== class2Attr
->kind
&& class1Attr
->flags
== class2Attr
->flags
&& class1Attr
->copyValue
== class2Attr
->copyValue
&& class1Attr
->setValue
== class2Attr
->setValue
) {
228 class1Attr
= class1
->attrs
[attrIndex
];
229 class2Attr
= class2
->attrs
[attrIndex
];
236 // if everything has checked out to this point, and we've hit the end of both class's attr list, then they're equal
237 if (class1Attr
== NULL
&& class2Attr
== NULL
) {
245 static bool ShouldRenameTable(const SecDbClass
* class1
, const SecDbClass
* class2
, int oldTableVersion
)
247 return oldTableVersion
< 10 || !DBClassesAreEqual(class1
, class2
);
250 #define SCHEMA_VERSION(schema) ((((schema)->minorVersion) << 8) | ((schema)->majorVersion))
251 #define VERSION_MAJOR(version) ((version) & 0xff)
252 #define VERSION_MINOR(version) (((version) >> 8) & 0xff)
253 #define VERSION_NEW(version) ((version) & 0xffff)
254 #define VERSION_OLD(version) (((version) >> 16) & 0xffff)
256 // Goes through all tables represented by old_schema and tries to migrate all items from them into new (current version) tables.
257 static bool UpgradeSchemaPhase1(SecDbConnectionRef dbt
, const SecDbSchema
*oldSchema
, CFErrorRef
*error
)
259 int oldVersion
= SCHEMA_VERSION(oldSchema
);
260 const SecDbSchema
*newSchema
= current_schema();
261 int newVersion
= SCHEMA_VERSION(newSchema
);
262 __block
bool ok
= true;
263 SecDbClass
const *const *oldClass
;
264 SecDbClass
const *const *newClass
;
265 SecDbQueryRef query
= NULL
;
266 CFMutableStringRef sql
= NULL
;
267 SecDbClass
* renamedOldClass
= NULL
;
268 #if TARGET_OS_EMBEDDED
269 __block
int64_t itemsMigrated
= 0;
270 struct timeval start
;
272 gettimeofday(&start
, NULL
);
275 // Rename existing tables to names derived from old schema names
276 sql
= CFStringCreateMutable(NULL
, 0);
277 bool oldClassDone
= false;
278 CFMutableArrayRef classIndexesForNewTables
= CFArrayCreateMutable(NULL
, 0, NULL
);
280 for (oldClass
= oldSchema
->classes
, newClass
= newSchema
->classes
;
281 *newClass
!= NULL
; classIndex
++, oldClass
++, newClass
++) {
282 oldClassDone
|= (*oldClass
) == NULL
; // Check if the new schema has more tables than the old
284 if (!oldClassDone
&& !CFEqual((*oldClass
)->name
, (*newClass
)->name
) && ShouldRenameTable(*oldClass
, *newClass
, oldSchema
->majorVersion
)) {
285 CFStringAppendFormat(sql
, NULL
, CFSTR("ALTER TABLE %@ RENAME TO %@_old;"), (*newClass
)->name
, (*oldClass
)->name
);
286 CFArrayAppendValue(classIndexesForNewTables
, (void*)(long)classIndex
);
288 } else if (!oldClassDone
&& !DBClassesAreEqual(*oldClass
, *newClass
)) {
289 CFStringAppendFormat(sql
, NULL
, CFSTR("ALTER TABLE %@ RENAME TO %@_old;"), (*newClass
)->name
, (*oldClass
)->name
);
290 CFArrayAppendValue(classIndexesForNewTables
, (void*)(long)classIndex
);
293 if(oldClassDone
&& *newClass
) {
294 // These should be no-ops, unless you're upgrading a previously-upgraded database with an invalid version number
295 CFStringAppendFormat(sql
, NULL
, CFSTR("DROP TABLE IF EXISTS %@;"), (*newClass
)->name
);
297 if (classIndexesForNewTables
) {
298 CFArrayAppendValue(classIndexesForNewTables
, (void*)(long)classIndex
);
303 if(CFStringGetLength(sql
) > 0) {
304 require_action_quiet(ok
&= SecDbExec(dbt
, sql
, error
), out
,
305 LKAReportKeychainUpgradeOutcomeWithError(oldVersion
, newVersion
, LKAKeychainUpgradeOutcomePhase1AlterTables
, error
? *error
: NULL
));
309 // Drop indices that that new schemas will use
310 sql
= CFStringCreateMutable(NULL
, 0);
311 for (newClass
= newSchema
->classes
; *newClass
!= NULL
; newClass
++) {
312 SecDbForEachAttrWithMask((*newClass
), desc
, kSecDbIndexFlag
| kSecDbInFlag
) {
313 CFStringAppendFormat(sql
, 0, CFSTR("DROP INDEX IF EXISTS %@%@;"), (*newClass
)->name
, desc
->name
);
314 if (desc
->kind
== kSecDbSyncAttr
) {
315 CFStringAppendFormat(sql
, 0, CFSTR("DROP INDEX IF EXISTS %@%@0;"), (*newClass
)->name
, desc
->name
);
319 require_action_quiet(ok
&= SecDbExec(dbt
, sql
, error
), out
,
320 LKAReportKeychainUpgradeOutcomeWithError(oldVersion
, newVersion
, LKAKeychainUpgradeOutcomePhase1DropIndices
, error
? *error
: NULL
));
323 // Create tables for new schema.
324 require_action_quiet(ok
&= SecItemDbCreateSchema(dbt
, newSchema
, classIndexesForNewTables
, false, error
), out
,
325 LKAReportKeychainUpgradeOutcomeWithError(oldVersion
, newVersion
, LKAKeychainUpgradeOutcomePhase1CreateSchema
, error
? *error
: NULL
));
326 // Go through all classes of current schema to transfer all items to new tables.
327 for (oldClass
= oldSchema
->classes
, newClass
= newSchema
->classes
;
328 *oldClass
!= NULL
&& *newClass
!= NULL
; oldClass
++, newClass
++) {
330 if (CFEqual((*oldClass
)->name
, (*newClass
)->name
) && DBClassesAreEqual(*oldClass
, *newClass
)) {
334 secnotice("upgr", "Upgrading table %@", (*oldClass
)->name
);
336 // Create a new 'old' class with a new 'old' name.
338 SecDbForEachAttr(*oldClass
, attr
) {
341 if(renamedOldClass
) {
342 CFReleaseNull(renamedOldClass
->name
);
343 free(renamedOldClass
);
345 renamedOldClass
= (SecDbClass
*) malloc(sizeof(SecDbClass
) + sizeof(SecDbAttr
*)*(count
+1));
346 renamedOldClass
->name
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("%@_old"), (*oldClass
)->name
);
347 renamedOldClass
->itemclass
= (*oldClass
)->itemclass
;
348 for(; count
>= 0; count
--) {
349 renamedOldClass
->attrs
[count
] = (*oldClass
)->attrs
[count
];
352 // SecDbItemSelect only works for item classes.
353 if((*oldClass
)->itemclass
) {
354 // Prepare query to iterate through all items in cur_class.
356 query_destroy(query
, NULL
);
357 require_quiet(query
= query_create(renamedOldClass
, SecMUSRGetAllViews(), NULL
, error
), out
);
359 ok
&= SecDbItemSelect(query
, dbt
, error
, ^bool(const SecDbAttr
*attr
) {
360 // We are interested in all attributes which are physically present in the DB.
361 return (attr
->flags
& kSecDbInFlag
) != 0;
362 }, ^bool(const SecDbAttr
*attr
) {
363 // No filtering please.
365 }, NULL
, NULL
, ^(SecDbItemRef item
, bool *stop
) {
366 CFErrorRef localError
= NULL
;
368 #if TARGET_OS_EMBEDDED
371 // Switch item to the new class.
372 item
->class = *newClass
;
374 if (isClassD(item
)) {
376 ok
&= SecDbItemEnsureDecrypted(item
, true, &localError
);
377 require_quiet(ok
, out
);
379 // Delete SHA1 field from the item, so that it is newly recalculated before storing
380 // the item into the new table.
381 require_quiet(ok
&= SecDbItemSetValue(item
, SecDbClassAttrWithKind(item
->class, kSecDbSHA1Attr
, error
),
382 kCFNull
, error
), out
);
384 // Leave item encrypted, do not ever try to decrypt it since it will fail.
385 item
->_edataState
= kSecDbItemAlwaysEncrypted
;
387 // Drop items with kSecAttrAccessGroupToken, as these items should not be there at all. Since agrp attribute
388 // is always stored as cleartext in the DB column, we can always rely on this attribute being present in item->attributes.
389 // <rdar://problem/33401870>
390 if (CFEqualSafe(SecDbItemGetCachedValueWithName(item
, kSecAttrAccessGroup
), kSecAttrAccessGroupToken
) &&
391 SecDbItemGetCachedValueWithName(item
, kSecAttrTokenID
) == NULL
) {
392 secnotice("upgr", "dropping item during schema upgrade due to agrp=com.apple.token: %@", item
);
394 // Insert new item into the new table.
395 if (!SecDbItemInsert(item
, dbt
, &localError
)) {
396 secerror("item: %@ insert during upgrade: %@", item
, localError
);
403 OSStatus status
= SecErrorGetOSStatus(localError
);
406 // continue to upgrade and don't propagate errors for insert failures
407 // that are typical of a single item failure
409 case errSecDuplicateItem
:
412 case errSecInteractionNotAllowed
:
413 case errSecAuthNeeded
:
416 // This does not mean the keychain is hosed, we just can't use it right now
418 case kAKSReturnNotReady
:
419 case kAKSReturnTimeout
:
421 case errSecNotAvailable
:
422 secnotice("upgr", "Bailing in phase 1 because AKS is unavailable: %@", localError
);
425 ok
&= CFErrorPropagate(CFRetainSafe(localError
), error
);
428 CFReleaseSafe(localError
);
435 require_action_quiet(ok
, out
,
436 LKAReportKeychainUpgradeOutcomeWithError(oldVersion
, newVersion
, LKAKeychainUpgradeOutcomePhase1Items
, error
? *error
: NULL
));
438 // This table does not contain secdb items, and must be transferred without using SecDbItemSelect.
439 // For now, this code does not support removing or renaming any columns, or adding any new non-null columns.
441 sql
= CFStringCreateMutable(NULL
, 0);
444 CFMutableStringRef columns
= CFStringCreateMutable(NULL
, 0);
446 SecDbForEachAttr(renamedOldClass
, attr
) {
448 CFStringAppendFormat(columns
, NULL
, CFSTR(","));
450 CFStringAppendFormat(columns
, NULL
, CFSTR("%@"), attr
->name
);
454 CFStringAppendFormat(sql
, NULL
, CFSTR("INSERT OR REPLACE INTO %@ (%@) SELECT %@ FROM %@;"), (*newClass
)->name
, columns
, columns
, renamedOldClass
->name
);
456 CFReleaseNull(columns
);
457 require_action_quiet(ok
&= SecDbExec(dbt
, sql
, error
), out
,
458 LKAReportKeychainUpgradeOutcomeWithError(oldVersion
, newVersion
, LKAKeychainUpgradeOutcomePhase1NonItems
, error
? *error
: NULL
));
462 // Remove old tables from the DB.
464 sql
= CFStringCreateMutable(NULL
, 0);
465 for (oldClass
= oldSchema
->classes
, newClass
= newSchema
->classes
;
466 *oldClass
!= NULL
&& *newClass
!= NULL
; oldClass
++, newClass
++) {
467 if (CFEqual((*oldClass
)->name
, (*newClass
)->name
) && DBClassesAreEqual(*oldClass
, *newClass
)) {
471 CFStringAppendFormat(sql
, NULL
, CFSTR("DROP TABLE %@_old;"), (*oldClass
)->name
);
474 if(CFStringGetLength(sql
) > 0) {
475 require_action_quiet(ok
&= SecDbExec(dbt
, sql
, error
), out
,
476 LKAReportKeychainUpgradeOutcomeWithError(oldVersion
, newVersion
, LKAKeychainUpgradeOutcomePhase1DropOld
, error
? *error
: NULL
));
479 #if TARGET_OS_EMBEDDED
480 measureUpgradePhase1(&start
, ok
, SecBucket2Significant(itemsMigrated
));
484 query_destroy(query
, NULL
);
487 CFReleaseNull(classIndexesForNewTables
);
488 if(renamedOldClass
) {
489 CFReleaseNull(renamedOldClass
->name
);
490 free(renamedOldClass
);
495 __thread SecDbConnectionRef dbt
= NULL
;
497 // Goes through all tables represented by old_schema and tries to migrate all items from them into new (current version) tables.
498 static bool UpgradeItemPhase2(SecDbConnectionRef inDbt
, bool *inProgress
, int oldVersion
, CFErrorRef
*error
) {
499 SecDbConnectionRef oldDbt
= dbt
;
501 __block
bool ok
= true;
502 SecDbQueryRef query
= NULL
;
503 #if TARGET_OS_EMBEDDED
504 __block
int64_t itemsMigrated
= 0;
505 struct timeval start
;
507 gettimeofday(&start
, NULL
);
510 // Go through all classes in new schema
511 const SecDbSchema
*newSchema
= current_schema();
512 int newVersion
= SCHEMA_VERSION(newSchema
);
513 for (const SecDbClass
*const *class = newSchema
->classes
; *class != NULL
&& !*inProgress
; class++) {
514 if(!((*class)->itemclass
)) {
515 //Don't try to decrypt non-item 'classes'
519 const SecDbAttr
*pdmn
= SecDbClassAttrWithKind(*class, kSecDbAccessAttr
, error
);
524 // Prepare query to go through all non-DK|DKU items
526 query_destroy(query
, NULL
);
528 require_action_quiet(query
= query_create(*class, SecMUSRGetAllViews(), NULL
, error
), out
, ok
= false);
529 ok
&= SecDbItemSelect(query
, dbt
, error
, NULL
, ^bool(const SecDbAttr
*attr
) {
530 // No simple per-attribute filtering.
532 }, ^bool(CFMutableStringRef sql
, bool *needWhere
) {
533 // Select only non-D-class items
534 SecDbAppendWhereOrAnd(sql
, needWhere
);
535 CFStringAppendFormat(sql
, NULL
, CFSTR("NOT %@ IN (?,?)"), pdmn
->name
);
537 }, ^bool(sqlite3_stmt
*stmt
, int col
) {
538 return SecDbBindObject(stmt
, col
++, kSecAttrAccessibleAlwaysPrivate
, error
) &&
539 SecDbBindObject(stmt
, col
++, kSecAttrAccessibleAlwaysThisDeviceOnlyPrivate
, error
);
540 }, ^(SecDbItemRef item
, bool *stop
) {
541 CFErrorRef localError
= NULL
;
543 #if TARGET_OS_EMBEDDED
548 if (SecDbItemEnsureDecrypted(item
, true, &localError
)) {
550 // Delete SHA1 field from the item, so that it is newly recalculated before storing
551 // the item into the new table.
552 require_quiet(ok
= SecDbItemSetValue(item
, SecDbClassAttrWithKind(item
->class, kSecDbSHA1Attr
, error
),
553 kCFNull
, &localError
), out
);
554 // Drop items with kSecAttrAccessGroupToken, as these items should not be there at all. Since agrp attribute
555 // is always stored as cleartext in the DB column, we can always rely on this attribute being present in item->attributes.
556 // <rdar://problem/33401870>
557 if (CFEqualSafe(SecDbItemGetCachedValueWithName(item
, kSecAttrAccessGroup
), kSecAttrAccessGroupToken
) &&
558 SecDbItemGetCachedValueWithName(item
, kSecAttrTokenID
) == NULL
) {
559 secnotice("upgr", "dropping item during item upgrade due to agrp=com.apple.token: %@", item
);
560 ok
= SecDbItemDelete(item
, dbt
, kCFBooleanFalse
, &localError
);
562 // Replace item with the new value in the table; this will cause the item to be decoded and recoded back,
563 // incl. recalculation of item's hash.
564 ok
= SecDbItemUpdate(item
, item
, dbt
, false, query
->q_uuid_from_primary_key
, &localError
);
569 CFIndex status
= CFErrorGetCode(localError
);
573 // Items producing errSecDecode are silently dropped - they are not decodable and lost forever.
574 // make sure we use a local error so that this error is not proppaged upward and cause a
575 // migration failure.
576 CFErrorRef deleteError
= NULL
;
577 (void)SecDbItemDelete(item
, dbt
, false, &deleteError
);
578 CFReleaseNull(deleteError
);
582 case errSecInteractionNotAllowed
:
583 // If we are still not able to decrypt the item because the class key is not released yet,
584 // remember that DB still needs phase2 migration to be run next time a connection is made. Also
585 // stop iterating next items, it would be just waste of time because the whole iteration will be run
586 // next time when this phase2 will be rerun.
587 LKAReportKeychainUpgradeOutcome(oldVersion
, newVersion
, LKAKeychainUpgradeOutcomeLocked
);
592 case errSecAuthNeeded
:
593 // errSecAuthNeeded means that it is an ACL-based item which requires authentication (or at least
594 // ACM context, which we do not have).
597 case SQLITE_CONSTRAINT
: // yeah...
598 if (!CFEqual(kSecDbErrorDomain
, CFErrorGetDomain(localError
))) {
599 secerror("Received SQLITE_CONSTRAINT with wrong error domain. Huh? Item: %@, error: %@", item
, localError
);
602 case errSecDuplicateItem
:
603 // continue to upgrade and don't propagate errors for insert failures
604 // that are typical of a single item failure
605 secnotice("upgr", "Ignoring duplicate item: %@", item
);
606 secdebug("upgr", "Duplicate item error: %@", localError
);
610 case kAKSReturnNotReady
:
611 case kAKSReturnTimeout
:
613 case errSecNotAvailable
:
614 *inProgress
= true; // We're not done, call me again later!
615 secnotice("upgr", "Bailing in phase 2 because AKS is unavailable: %@", localError
);
618 // Other errors should abort the migration completely.
619 ok
= CFErrorPropagate(CFRetainSafe(localError
), error
);
625 CFReleaseSafe(localError
);
626 *stop
= *stop
|| !ok
;
629 require_action(ok
, out
, LKAReportKeychainUpgradeOutcomeWithError(oldVersion
, newVersion
, LKAKeychainUpgradeOutcomePhase2
, error
? *error
: NULL
));
632 #if TARGET_OS_EMBEDDED
633 measureUpgradePhase2(&start
, SecBucket2Significant(itemsMigrated
));
638 query_destroy(query
, NULL
);
644 static bool SecKeychainDbUpgradeFromVersion(SecDbConnectionRef dbt
, int version
, bool *inProgress
, CFErrorRef
*error
) {
645 __block
bool didPhase2
= false;
646 __block
bool ok
= true;
647 __block CFErrorRef localError
= NULL
;
652 const SecDbSchema
*newSchema
= current_schema();
653 int newVersion
= SCHEMA_VERSION(newSchema
);
654 bool skipped_upgrade
= false;
656 // If DB schema is the one we want, we are done.
657 require_action_quiet(SCHEMA_VERSION(newSchema
) != version
, out
, skipped_upgrade
= true);
659 // Check if the schema of the database on disk is the same major, but newer version then what we have
660 // in code, lets just skip this since a newer version of the OS have upgrade it. Since its the same
661 // major, its a promise that it will be compatible.
662 if (newSchema
->majorVersion
== VERSION_MAJOR(version
) && newSchema
->minorVersion
< VERSION_MINOR(version
)) {
663 secnotice("upgr", "skipping upgrade since minor is newer");
667 ok
&= SecDbTransaction(dbt
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
668 CFStringRef sql
= NULL
;
669 bool didPhase1
= false;
671 // Get version again once we start a transaction, someone else might change the migration state.
673 require_quiet(ok
= SecKeychainDbGetVersion(dbt
, &version2
, &localError
), out
);
674 // Check if someone has raced us to the migration of the database
675 require_action(version
== version2
, out
, CFReleaseNull(localError
); ok
= true);
677 require_quiet(SCHEMA_VERSION(newSchema
) != version2
, out
);
679 // If this is empty database, just create table according to schema and be done with it.
680 require_action_quiet(version2
!= 0, out
, ok
= SecItemDbCreateSchema(dbt
, newSchema
, NULL
, true, &localError
);
681 LKAReportKeychainUpgradeOutcomeWithError(version2
, newVersion
, LKAKeychainUpgradeOutcomeNewDb
, localError
));
683 int oldVersion
= VERSION_OLD(version2
);
684 version2
= VERSION_NEW(version2
);
686 require_action_quiet(version2
== SCHEMA_VERSION(newSchema
) || oldVersion
== 0, out
,
687 ok
= SecDbError(SQLITE_CORRUPT
, &localError
,
688 CFSTR("Half migrated but obsolete DB found: found 0x%x(0x%x) but 0x%x is needed"),
689 version2
, oldVersion
, SCHEMA_VERSION(newSchema
));
690 LKAReportKeychainUpgradeOutcome(version2
, newVersion
, LKAKeychainUpgradeOutcomeObsoleteDb
));
692 // Check whether we have both old and new tables in the DB.
693 if (oldVersion
== 0) {
694 // Pure old-schema migration attempt, with full blown table renames etc (a.k.a. phase1)
695 oldVersion
= version2
;
696 version2
= SCHEMA_VERSION(newSchema
);
698 // Find schema for old database.
699 const SecDbSchema
*oldSchema
= NULL
;
700 for (const SecDbSchema
* const *pschema
= all_schemas(); *pschema
; ++pschema
) {
701 if (SCHEMA_VERSION((*pschema
)) == oldVersion
) {
702 oldSchema
= *pschema
;
707 // If we are attempting to upgrade from a version for which we have no schema, fail.
708 require_action_quiet(oldSchema
!= NULL
, out
,
709 ok
= SecDbError(SQLITE_CORRUPT
, &localError
, CFSTR("no schema for version: 0x%x"), oldVersion
);
710 secerror("no schema for version 0x%x", oldVersion
);
711 LKAReportKeychainUpgradeOutcome(version2
, newVersion
, LKAKeychainUpgradeOutcomeNoSchema
));
713 secnotice("upgr", "Upgrading from version 0x%x to 0x%x", oldVersion
, SCHEMA_VERSION(newSchema
));
714 SecSignpostStart(SecSignpostUpgradePhase1
);
715 require_action(ok
= UpgradeSchemaPhase1(dbt
, oldSchema
, &localError
), out
, secerror("upgrade: Upgrade phase1 failed: %@", localError
));
716 SecSignpostStop(SecSignpostUpgradePhase1
);
722 CFErrorRef phase2Error
= NULL
;
724 SecSignpostStart(SecSignpostUpgradePhase2
);
726 // Lets try to go through non-D-class items in new tables and apply decode/encode on them
727 // If this fails the error will be ignored after doing a phase1 since but not in the second
728 // time when we are doing phase2.
729 ok
= UpgradeItemPhase2(dbt
, inProgress
, version2
, &phase2Error
);
734 CFReleaseNull(phase2Error
);
736 SecErrorPropagate(phase2Error
, &localError
);
739 require_action(ok
, out
, secerror("upgrade: Upgrade phase2 (%d) failed: %@", didPhase1
, localError
));
742 // If either migration path we did reported that the migration was complete, signalize that
743 // in the version database by cleaning oldVersion (which is stored in upper halfword of the version)
744 secnotice("upgr", "Done upgrading from version 0x%x to 0x%x", oldVersion
, SCHEMA_VERSION(newSchema
));
748 SecSignpostStop(SecSignpostUpgradePhase2
);
752 // Update database version table.
753 uint32_t major
= (VERSION_MAJOR(version2
)) | (VERSION_MAJOR(oldVersion
) << 16);
754 uint32_t minor
= (VERSION_MINOR(version2
)) | (VERSION_MINOR(oldVersion
) << 16);
755 secnotice("upgr", "Upgrading saving version major 0x%x minor 0x%x", major
, minor
);
756 sql
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("UPDATE tversion SET version='%d', minor='%d'"),
758 require_action_quiet(ok
= SecDbExec(dbt
, sql
, &localError
), out
, secerror("upgrade: Setting version failed: %@", localError
));
762 secerror("upgrade: SecDB upgrade failed: %@", localError
);
768 if (ok
&& didPhase2
) {
769 #if TARGET_OS_EMBEDDED
770 SecADSetValueForScalarKey(CFSTR("com.apple.keychain.migration-success"), 1);
775 if (!ok
|| localError
) {
776 // TODO: This logic should be inverted to a do-not-corrupt-unless default, <rdar://problem/29771874>
778 * We assume that database is corrupt at this point, but we need to
779 * check if the error we got isn't severe enough to mark the database as corrupt.
780 * In those cases we opt out of corrupting the database.
782 bool markedCorrupt
= true;
785 secwarning("upgrade: error has been set but status is true");
788 secerror("upgrade: error occurred, considering marking database as corrupt: %@", localError
);
790 CFStringRef domain
= CFErrorGetDomain(localError
);
791 CFIndex code
= CFErrorGetCode(localError
);
793 if ((CFEqualSafe(domain
, kSecDbErrorDomain
) &&
794 ((code
& 0xff) == SQLITE_LOCKED
|| (code
& 0xff) == SQLITE_BUSY
|| (code
& 0xff) == SQLITE_FULL
)) ||
796 code
== kAKSReturnNotReady
|| code
== kAKSReturnTimeout
||
798 code
== errSecNotAvailable
)
800 secerror("upgrade: not marking keychain database corrupt for error: %@", localError
);
801 markedCorrupt
= false;
802 CFReleaseNull(localError
);
804 secerror("upgrade: unable to complete upgrade, marking DB as corrupt: %@", localError
);
807 secerror("upgrade: unable to complete upgrade and no error object returned, marking DB as corrupt");
810 secerror("upgrade: marking database as corrupt");
811 SecDbCorrupt(dbt
, localError
);
812 #if TARGET_OS_EMBEDDED
813 SecADSetValueForScalarKey(CFSTR("com.apple.keychain.migration-failure"), 1);
817 // Things seemed to go okay!
819 LKAReportKeychainUpgradeOutcome(version
, newVersion
, LKAKeychainUpgradeOutcomeSuccess
);
822 //If we're done here, we should opportunistically re-add all indices (just in case)
823 if(skipped_upgrade
|| didPhase2
) {
824 // Create indices, ignoring all errors
825 for (SecDbClass
const* const* newClass
= newSchema
->classes
; *newClass
; ++newClass
) {
826 SecDbForEachAttrWithMask((*newClass
), desc
, kSecDbIndexFlag
| kSecDbInFlag
) {
827 CFStringRef sql
= NULL
;
828 CFErrorRef localError
= NULL
;
831 if (desc
->kind
== kSecDbSyncAttr
) {
832 // 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.
833 sql
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("DROP INDEX IF EXISTS %@%@; CREATE INDEX IF NOT EXISTS %@%@0 on %@(%@) WHERE %@=0;"),
834 (*newClass
)->name
, desc
->name
, (*newClass
)->name
, desc
->name
, (*newClass
)->name
, desc
->name
, desc
->name
);
836 sql
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("CREATE INDEX IF NOT EXISTS %@%@ ON %@(%@);"), (*newClass
)->name
, desc
->name
, (*newClass
)->name
, desc
->name
);
838 localOk
&= SecDbExec(dbt
, sql
, &localError
);
842 secerror("upgrade: unable to opportunistically create index (%@,%@): %@", (*newClass
)->name
, desc
->name
, localError
);
844 CFReleaseNull(localError
);
851 *error
= (CFErrorRef
)CFRetain(localError
);
853 CFReleaseNull(localError
);
859 static bool accessGroupIsNetworkExtensionAndClientIsEntitled(CFStringRef accessGroup
, SecurityClient
* client
)
861 return client
&& client
->canAccessNetworkExtensionAccessGroups
&& accessGroup
&& CFStringHasSuffix(accessGroup
, kSecNetworkExtensionAccessGroupSuffix
);
864 /* AUDIT[securityd](done):
865 accessGroup (ok) is a caller provided, non NULL CFTypeRef.
867 Return true iff accessGroup is allowable according to accessGroups.
869 bool accessGroupsAllows(CFArrayRef accessGroups
, CFStringRef accessGroup
, SecurityClient
* client
) {
870 /* NULL accessGroups is wildcard. */
873 /* Make sure we have a string. */
874 if (!isString(accessGroup
))
877 /* Having the special accessGroup "*" allows access to all accessGroups. */
878 CFRange range
= { 0, CFArrayGetCount(accessGroups
) };
880 (CFArrayContainsValue(accessGroups
, range
, accessGroup
) ||
881 CFArrayContainsValue(accessGroups
, range
, CFSTR("*")) ||
882 accessGroupIsNetworkExtensionAndClientIsEntitled(accessGroup
, client
)))
888 bool itemInAccessGroup(CFDictionaryRef item
, CFArrayRef accessGroups
) {
889 return accessGroupsAllows(accessGroups
,
890 CFDictionaryGetValue(item
, kSecAttrAccessGroup
), NULL
);
894 static CF_RETURNS_RETAINED CFDataRef
SecServerExportBackupableKeychain(SecDbConnectionRef dbt
,
895 SecurityClient
*client
,
896 keybag_handle_t src_keybag
, keybag_handle_t dest_keybag
, CFErrorRef
*error
) {
897 CFDataRef data_out
= NULL
;
899 SecSignpostStart(SecSignpostBackupKeychainBackupable
);
901 /* Export everything except the items for which SecItemIsSystemBound()
903 CFDictionaryRef keychain
= SecServerCopyKeychainPlist(dbt
, client
,
904 src_keybag
, dest_keybag
, kSecBackupableItemFilter
,
907 data_out
= CFPropertyListCreateData(kCFAllocatorDefault
, keychain
,
908 kCFPropertyListBinaryFormat_v1_0
,
912 SecSignpostStop(SecSignpostBackupKeychainBackupable
);
917 static bool SecServerImportBackupableKeychain(SecDbConnectionRef dbt
,
918 SecurityClient
*client
,
919 keybag_handle_t src_keybag
,
920 keybag_handle_t dest_keybag
,
924 return kc_transaction(dbt
, error
, ^{
926 CFDictionaryRef keychain
;
928 SecSignpostStart(SecSignpostRestoreKeychainBackupable
);
930 keychain
= CFPropertyListCreateWithData(kCFAllocatorDefault
, data
,
931 kCFPropertyListImmutable
, NULL
,
934 if (isDictionary(keychain
)) {
935 ok
= SecServerImportKeychainInPlist(dbt
,
940 kSecBackupableItemFilter
,
941 false, // Restoring backup should not remove stuff that got into the keychain before us
944 ok
= SecError(errSecParam
, error
, CFSTR("import: keychain is not a dictionary"));
949 SecSignpostStop(SecSignpostRestoreKeychainBackupable
);
957 * Similar to ks_open_keybag, but goes through MKB interface
959 static bool mkb_open_keybag(CFDataRef keybag
, CFDataRef password
, MKBKeyBagHandleRef
*handle
, bool emcs
, CFErrorRef
*error
) {
961 MKBKeyBagHandleRef mkbhandle
= NULL
;
963 rc
= MKBKeyBagCreateWithData(keybag
, &mkbhandle
);
964 if (rc
!= kMobileKeyBagSuccess
) {
965 return SecKernError(rc
, error
, CFSTR("MKBKeyBagCreateWithData failed: %d"), rc
);
969 rc
= MKBKeyBagUnlock(mkbhandle
, password
);
970 if (rc
!= kMobileKeyBagSuccess
) {
971 CFRelease(mkbhandle
);
972 return SecKernError(rc
, error
, CFSTR("failed to unlock bag: %d"), rc
);
975 secnotice("keychainbackup", "skipping keybag unlock for EMCS");
985 static CFDataRef
SecServerKeychainCreateBackup(SecDbConnectionRef dbt
, SecurityClient
*client
, CFDataRef keybag
,
986 CFDataRef password
, bool emcs
, CFErrorRef
*error
) {
987 CFDataRef backup
= NULL
;
988 keybag_handle_t backup_keybag
;
990 SecSignpostStart(SecSignpostBackupOpenKeybag
);
993 MKBKeyBagHandleRef mkbhandle
= NULL
;
994 require(mkb_open_keybag(keybag
, password
, &mkbhandle
, emcs
, error
), out
);
996 require_noerr(MKBKeyBagGetAKSHandle(mkbhandle
, &backup_keybag
), out
);
999 backup_keybag
= KEYBAG_NONE
;
1001 SecSignpostStop(SecSignpostBackupOpenKeybag
);
1002 SecSignpostStart(SecSignpostBackupKeychain
);
1004 /* Export from system keybag to backup keybag. */
1005 backup
= SecServerExportBackupableKeychain(dbt
, client
, KEYBAG_DEVICE
, backup_keybag
, error
);
1009 SecSignpostStop(SecSignpostBackupOpenKeybag
);
1012 CFRelease(mkbhandle
);
1017 static bool SecServerKeychainRestore(SecDbConnectionRef dbt
,
1018 SecurityClient
*client
,
1025 keybag_handle_t backup_keybag
;
1028 SecSignpostStart(SecSignpostRestoreOpenKeybag
);
1030 MKBKeyBagHandleRef mkbhandle
= NULL
;
1031 require(mkb_open_keybag(keybag
, password
, &mkbhandle
, false, error
), out
);
1033 require_noerr(MKBKeyBagGetAKSHandle(mkbhandle
, &backup_keybag
), out
);
1035 backup_keybag
= KEYBAG_NONE
;
1037 SecSignpostStop(SecSignpostRestoreOpenKeybag
);
1038 SecSignpostStart(SecSignpostRestoreKeychain
);
1040 /* Import from backup keybag to system keybag. */
1041 require(SecServerImportBackupableKeychain(dbt
, client
, backup_keybag
, KEYBAG_DEVICE
, backup
, error
), out
);
1045 SecSignpostStop(SecSignpostRestoreKeychain
);
1048 CFRelease(mkbhandle
);
1052 secwarning("Restore completed sucessfully");
1054 secwarning("Restore failed with: %@", error
? *error
: NULL
);
1061 // MARK - External SPI support code.
1063 CFStringRef
__SecKeychainCopyPath(void) {
1064 CFStringRef kcRelPath
= NULL
;
1066 kcRelPath
= CFSTR("keychain-2.db");
1068 kcRelPath
= CFSTR("keychain-2-debug.db");
1071 CFStringRef kcPath
= NULL
;
1072 CFURLRef kcURL
= SecCopyURLForFileInKeychainDirectory(kcRelPath
);
1074 kcPath
= CFURLCopyFileSystemPath(kcURL
, kCFURLPOSIXPathStyle
);
1081 // MARK: kc_dbhandle init and reset
1083 SecDbRef
SecKeychainDbCreate(CFStringRef path
, CFErrorRef
* error
) {
1084 __block CFErrorRef localerror
= NULL
;
1086 SecDbRef kc
= SecDbCreate(path
, 0600, true, true, true, true, kSecDbMaxIdleHandles
,
1087 ^bool (SecDbRef db
, SecDbConnectionRef dbconn
, bool didCreate
, bool *callMeAgainForNextConnection
, CFErrorRef
*error
)
1089 // Upgrade from version 0 means create the schema in empty db.
1093 ok
= SecKeychainDbGetVersion(dbconn
, &version
, error
);
1095 ok
= ok
&& SecKeychainDbUpgradeFromVersion(dbconn
, version
, callMeAgainForNextConnection
, error
);
1097 secerror("Upgrade %sfailed: %@", didCreate
? "from v0 " : "", error
? *error
: NULL
);
1099 localerror
= error
? *error
: NULL
;
1102 // This block might get called many, many times due to callMeAgainForNextConnection.
1103 // When we no longer want to be called, we believe we're done. Begin the rest of initialization.
1104 if( !callMeAgainForNextConnection
|| !(*callMeAgainForNextConnection
)) {
1105 SecKeychainDbInitialize(db
);
1113 SecDbSetCorruptionReset(kc
, ^{
1114 SecDbResetMetadataKeys();
1119 *error
= localerror
;
1125 SecDbRef
SecKeychainDbInitialize(SecDbRef db
) {
1128 if(SecCKKSIsEnabled()) {
1129 // This needs to be async, otherwise we get hangs between securityd, cloudd, and apsd
1130 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT
, 0), ^{
1131 SecCKKSInitialize(db
);
1140 static SecDbRef _kc_dbhandle
= NULL
;
1141 static dispatch_queue_t _kc_dbhandle_dispatch
= NULL
;
1142 static dispatch_once_t _kc_dbhandle_dispatch_onceToken
= 0;
1143 static dispatch_queue_t
get_kc_dbhandle_dispatch() {
1144 dispatch_once(&_kc_dbhandle_dispatch_onceToken
, ^{
1145 _kc_dbhandle_dispatch
= dispatch_queue_create("sec_kc_dbhandle", DISPATCH_QUEUE_SERIAL
);
1148 return _kc_dbhandle_dispatch
;
1151 static bool kc_dbhandle_init(CFErrorRef
* error
) {
1152 SecDbRef oldHandle
= _kc_dbhandle
;
1153 _kc_dbhandle
= NULL
;
1154 CFStringRef dbPath
= __SecKeychainCopyPath();
1156 _kc_dbhandle
= SecKeychainDbCreate(dbPath
, error
);
1159 secerror("no keychain path available");
1162 secerror("replaced %@ with %@", oldHandle
, _kc_dbhandle
);
1163 CFRelease(oldHandle
);
1165 // Having a dbhandle means we succeeded.
1166 return !!_kc_dbhandle
;
1169 static SecDbRef
kc_dbhandle(CFErrorRef
* error
)
1171 dispatch_sync(get_kc_dbhandle_dispatch(), ^{
1172 if(_kc_dbhandle
== NULL
) {
1173 _SecDbServerSetup();
1174 kc_dbhandle_init(error
);
1177 return _kc_dbhandle
;
1180 /* For whitebox testing only */
1181 void SecKeychainDbReset(dispatch_block_t inbetween
)
1183 dispatch_sync(get_kc_dbhandle_dispatch(), ^{
1184 CFReleaseNull(_kc_dbhandle
);
1185 SecDbResetMetadataKeys();
1192 static bool kc_acquire_dbt(bool writeAndRead
, SecDbConnectionRef
* dbconn
, CFErrorRef
*error
) {
1193 SecDbRef db
= kc_dbhandle(error
);
1195 if(error
&& !(*error
)) {
1196 SecError(errSecDataNotAvailable
, error
, CFSTR("failed to get a db handle"));
1201 return SecDbConnectionAcquireRefMigrationSafe(db
, !writeAndRead
, dbconn
, error
);
1204 /* Return a per thread dbt handle for the keychain. If create is true create
1205 the database if it does not yet exist. If it is false, just return an
1206 error if it fails to auto-create. */
1207 bool kc_with_dbt(bool writeAndRead
, CFErrorRef
*error
, bool (^perform
)(SecDbConnectionRef dbt
))
1209 return kc_with_custom_db(writeAndRead
, true, NULL
, error
, perform
);
1212 bool kc_with_dbt_non_item_tables(bool writeAndRead
, CFErrorRef
* error
, bool (^perform
)(SecDbConnectionRef dbt
))
1214 return kc_with_custom_db(writeAndRead
, false, NULL
, error
, perform
);
1217 bool kc_with_custom_db(bool writeAndRead
, bool usesItemTables
, SecDbRef db
, CFErrorRef
*error
, bool (^perform
)(SecDbConnectionRef dbt
))
1219 if (db
&& db
!= kc_dbhandle(error
)) {
1220 __block
bool result
= false;
1222 return SecDbPerformWrite(db
, error
, ^(SecDbConnectionRef dbconn
) {
1223 result
= perform(dbconn
);
1227 return SecDbPerformRead(db
, error
, ^(SecDbConnectionRef dbconn
) {
1228 result
= perform(dbconn
);
1235 // The kc_with_dbt upthread will clean this up when it's done.
1236 return perform(dbt
);
1239 if (writeAndRead
&& usesItemTables
) {
1240 SecItemDataSourceFactoryGetDefault();
1244 if (kc_acquire_dbt(writeAndRead
, &dbt
, error
)) {
1246 SecDbConnectionRelease(dbt
);
1253 items_matching_issuer_parent(SecDbConnectionRef dbt
, CFArrayRef accessGroups
, CFDataRef musrView
,
1254 CFDataRef issuer
, CFArrayRef issuers
, int recurse
)
1257 CFArrayRef results
= NULL
;
1261 if (CFArrayContainsValue(issuers
, CFRangeMake(0, CFArrayGetCount(issuers
)), issuer
))
1264 /* XXX make musr supported */
1265 const void *keys
[] = { kSecClass
, kSecReturnRef
, kSecAttrSubject
};
1266 const void *vals
[] = { kSecClassCertificate
, kCFBooleanTrue
, issuer
};
1267 CFDictionaryRef query
= CFDictionaryCreate(kCFAllocatorDefault
, keys
, vals
, array_size(keys
), NULL
, NULL
);
1272 CFErrorRef localError
= NULL
;
1273 q
= query_create_with_limit(query
, musrView
, kSecMatchUnlimited
, &localError
);
1276 s3dl_copy_matching(dbt
, q
, (CFTypeRef
*)&results
, accessGroups
, &localError
);
1277 query_destroy(q
, &localError
);
1280 secerror("items matching issuer parent: %@", localError
);
1281 CFReleaseNull(localError
);
1285 count
= CFArrayGetCount(results
);
1286 for (i
= 0; (i
< count
) && !found
; i
++) {
1287 CFDictionaryRef cert_dict
= (CFDictionaryRef
)CFArrayGetValueAtIndex(results
, i
);
1288 CFDataRef cert_issuer
= CFDictionaryGetValue(cert_dict
, kSecAttrIssuer
);
1289 if (CFEqual(cert_issuer
, issuer
))
1292 found
= items_matching_issuer_parent(dbt
, accessGroups
, musrView
, cert_issuer
, issuers
, recurse
);
1294 CFReleaseSafe(results
);
1300 _FilterWithPolicy(SecPolicyRef policy
, CFDateRef date
, SecCertificateRef cert
)
1302 CFDictionaryRef props
= NULL
;
1303 CFArrayRef keychains
= NULL
;
1304 CFArrayRef anchors
= NULL
;
1305 CFArrayRef certs
= NULL
;
1306 CFArrayRef chain
= NULL
;
1307 SecTrustRef trust
= NULL
;
1309 SecTrustResultType trustResult
;
1310 Boolean needChain
= false;
1311 __block
bool ok
= false;
1313 if (!policy
|| !cert
) return false;
1315 certs
= CFArrayCreate(NULL
, (const void **)&cert
, (CFIndex
)1, &kCFTypeArrayCallBacks
);
1316 require_noerr_quiet(SecTrustCreateWithCertificates(certs
, policy
, &trust
), cleanup
);
1318 /* Set evaluation date, if specified (otherwise current date is implied) */
1319 if (date
&& (CFGetTypeID(date
) == CFDateGetTypeID())) {
1320 require_noerr_quiet(SecTrustSetVerifyDate(trust
, date
), cleanup
);
1323 /* Check whether this is the X509 Basic policy, which means chain building */
1324 props
= SecPolicyCopyProperties(policy
);
1326 CFTypeRef oid
= (CFTypeRef
) CFDictionaryGetValue(props
, kSecPolicyOid
);
1327 if (oid
&& (CFEqual(oid
, kSecPolicyAppleX509Basic
) ||
1328 CFEqual(oid
, kSecPolicyAppleRevocation
))) {
1334 require_noerr_quiet(SecTrustEvaluateLeafOnly(trust
, &trustResult
), cleanup
);
1336 require_noerr_quiet(SecTrustEvaluate(trust
, &trustResult
), cleanup
);
1339 require_quiet((trustResult
== kSecTrustResultProceed
||
1340 trustResult
== kSecTrustResultUnspecified
||
1341 trustResult
== kSecTrustResultRecoverableTrustFailure
), cleanup
);
1344 #if TARGET_OS_IPHONE
1345 CFArrayRef properties
= SecTrustCopyProperties(trust
);
1347 CFArrayRef properties
= SecTrustCopyProperties_ios(trust
);
1350 CFArrayForEach(properties
, ^(const void *property
) {
1351 CFDictionaryForEach((CFDictionaryRef
)property
, ^(const void *key
, const void *value
) {
1352 if (CFEqual((CFTypeRef
)key
, kSecPropertyKeyType
) && CFEqual((CFTypeRef
)value
, kSecPropertyTypeError
))
1356 CFRelease(properties
);
1360 if(props
) CFRelease(props
);
1361 if(chain
) CFRelease(chain
);
1362 if(anchors
) CFRelease(anchors
);
1363 if(keychains
) CFRelease(keychains
);
1364 if(certs
) CFRelease(certs
);
1365 if(trust
) CFRelease(trust
);
1371 _FilterWithDate(CFDateRef validOnDate
, SecCertificateRef cert
)
1373 if (!validOnDate
|| !cert
) return false;
1375 CFAbsoluteTime at
, nb
, na
;
1376 at
= CFDateGetAbsoluteTime((CFDateRef
)validOnDate
);
1379 nb
= SecCertificateNotValidBefore(cert
);
1380 na
= SecCertificateNotValidAfter(cert
);
1382 if (nb
== 0 || na
== 0 || nb
== na
) {
1384 secnotice("FilterWithDate", "certificate cannot operate");
1388 secnotice("FilterWithDate", "certificate is not valid yet");
1392 secnotice("FilterWithDate", "certificate expired");
1399 _FilterWithTrust(Boolean trustedOnly
, SecCertificateRef cert
)
1401 if (!cert
) return false;
1402 if (!trustedOnly
) return true;
1405 CFArrayRef certArray
= CFArrayCreate(NULL
, (const void**)&cert
, 1, &kCFTypeArrayCallBacks
);
1406 SecTrustRef trust
= NULL
;
1407 SecPolicyRef policy
= SecPolicyCreateBasicX509();
1408 require_quiet(policy
, out
);
1410 require_noerr_quiet(SecTrustCreateWithCertificates(certArray
, policy
, &trust
), out
);
1411 SecTrustResultType trustResult
;
1412 require_noerr_quiet(SecTrustEvaluate(trust
, &trustResult
), out
);
1414 require_quiet((trustResult
== kSecTrustResultProceed
||
1415 trustResult
== kSecTrustResultUnspecified
), out
);
1418 CFReleaseSafe(trust
);
1419 CFReleaseSafe(policy
);
1420 CFReleaseSafe(certArray
);
1424 static SecCertificateRef
1425 CopyCertificateFromItem(Query
*q
, CFDictionaryRef item
) {
1426 SecCertificateRef certRef
= NULL
;
1427 CFDictionaryRef itemValue
= NULL
;
1429 CFTypeRef tokenID
= NULL
;
1430 CFDataRef certData
= NULL
;
1431 if (q
->q_class
== identity_class()) {
1432 certData
= CFDictionaryGetValue(item
, kSecAttrIdentityCertificateData
);
1433 tokenID
= CFDictionaryGetValue(item
, kSecAttrIdentityCertificateTokenID
);
1434 } else if (q
->q_class
== cert_class()) {
1435 certData
= CFDictionaryGetValue(item
, kSecValueData
);
1436 tokenID
= CFDictionaryGetValue(item
, kSecAttrTokenID
);
1439 require_quiet(certData
, out
);
1440 if (tokenID
!= NULL
) {
1441 CFErrorRef error
= NULL
;
1442 itemValue
= SecTokenItemValueCopy(certData
, &error
);
1443 require_action_quiet(itemValue
, out
, { secerror("function SecTokenItemValueCopy failed with: %@", error
); CFReleaseSafe(error
); });
1444 CFDataRef tokenCertData
= CFDictionaryGetValue(itemValue
, kSecTokenValueDataKey
);
1445 require_action_quiet(tokenCertData
, out
, { secerror("token item doesn't contain token value data");});
1446 certRef
= SecCertificateCreateWithData(kCFAllocatorDefault
, tokenCertData
);
1449 certRef
= SecCertificateCreateWithData(kCFAllocatorDefault
, certData
);
1452 CFReleaseNull(itemValue
);
1456 bool match_item(SecDbConnectionRef dbt
, Query
*q
, CFArrayRef accessGroups
, CFDictionaryRef item
)
1459 SecCertificateRef certRef
= NULL
;
1460 if (q
->q_match_issuer
) {
1461 CFDataRef issuer
= CFDictionaryGetValue(item
, kSecAttrIssuer
);
1462 if (!items_matching_issuer_parent(dbt
, accessGroups
, q
->q_musrView
, issuer
, q
->q_match_issuer
, 10 /*max depth*/))
1466 if (q
->q_match_policy
&& (q
->q_class
== identity_class() || q
->q_class
== cert_class())) {
1468 certRef
= CopyCertificateFromItem(q
, item
);
1469 require_quiet(certRef
, out
);
1470 require_quiet(_FilterWithPolicy(q
->q_match_policy
, q
->q_match_valid_on_date
, certRef
), out
);
1473 if (q
->q_match_valid_on_date
&& (q
->q_class
== identity_class() || q
->q_class
== cert_class())) {
1475 certRef
= CopyCertificateFromItem(q
, item
);
1476 require_quiet(certRef
, out
);
1477 require_quiet(_FilterWithDate(q
->q_match_valid_on_date
, certRef
), out
);
1480 if (q
->q_match_trusted_only
&& (q
->q_class
== identity_class() || q
->q_class
== cert_class())) {
1482 certRef
= CopyCertificateFromItem(q
, item
);
1483 require_quiet(certRef
, out
);
1484 require_quiet(_FilterWithTrust(CFBooleanGetValue(q
->q_match_trusted_only
), certRef
), out
);
1487 /* Add future match checks here. */
1490 CFReleaseSafe(certRef
);
1494 /****************************************************************************
1495 **************** Beginning of Externally Callable Interface ****************
1496 ****************************************************************************/
1498 static bool SecEntitlementError(OSStatus status
, CFErrorRef
*error
)
1501 #define SEC_ENTITLEMENT_WARNING CFSTR("com.apple.application-identifier, com.apple.security.application-groups nor keychain-access-groups")
1503 #define SEC_ENTITLEMENT_WARNING CFSTR("application-identifier nor keychain-access-groups")
1506 return SecError(errSecMissingEntitlement
, error
, CFSTR("Client has neither %@ entitlements"), SEC_ENTITLEMENT_WARNING
);
1509 static CFStringRef
CopyAccessGroupForRowID(sqlite_int64 rowID
, CFStringRef itemClass
)
1511 __block CFStringRef accessGroup
= NULL
;
1513 __block CFErrorRef error
= NULL
;
1514 bool ok
= kc_with_dbt(false, &error
, ^bool(SecDbConnectionRef dbt
) {
1515 CFStringRef table
= CFEqual(itemClass
, kSecClassIdentity
) ? kSecClassCertificate
: itemClass
;
1516 CFStringRef sql
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("SELECT agrp FROM %@ WHERE rowid == %u"), table
, (unsigned int)rowID
);
1517 bool dbOk
= SecDbWithSQL(dbt
, sql
, &error
, ^bool(sqlite3_stmt
*stmt
) {
1518 bool rowOk
= SecDbForEach(dbt
, stmt
, &error
, ^bool(int row_index
) {
1519 accessGroup
= CFStringCreateWithBytes(NULL
, sqlite3_column_blob(stmt
, 0), sqlite3_column_bytes(stmt
, 0), kCFStringEncodingUTF8
, false);
1520 return accessGroup
!= NULL
;
1523 return (bool)(rowOk
&& accessGroup
!= NULL
);
1527 return (bool)(dbOk
&& accessGroup
);
1534 CFReleaseNull(accessGroup
);
1539 /* AUDIT[securityd](done):
1540 query (ok) is a caller provided dictionary, only its cf type has been checked.
1543 SecItemServerCopyMatching(CFDictionaryRef query
, CFTypeRef
*result
,
1544 SecurityClient
*client
, CFErrorRef
*error
)
1546 CFArrayRef accessGroups
= client
->accessGroups
;
1547 CFMutableArrayRef mutableAccessGroups
= NULL
;
1550 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
))) {
1551 return SecEntitlementError(errSecMissingEntitlement
, error
);
1554 SecSignpostStart(SecSignpostSecItemCopyMatching
);
1556 if (client
->canAccessNetworkExtensionAccessGroups
) {
1557 CFDataRef persistentRef
= CFDictionaryGetValue(query
, kSecValuePersistentRef
);
1558 CFStringRef itemClass
= NULL
;
1559 sqlite_int64 itemRowID
= 0;
1560 if (persistentRef
&& _SecItemParsePersistentRef(persistentRef
, &itemClass
, &itemRowID
, NULL
)) {
1561 CFStringRef accessGroup
= CopyAccessGroupForRowID(itemRowID
, itemClass
);
1562 if (accessGroup
&& CFStringHasSuffix(accessGroup
, kSecNetworkExtensionAccessGroupSuffix
) && !CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), accessGroup
)) {
1563 mutableAccessGroups
= CFArrayCreateMutableCopy(NULL
, 0, accessGroups
);
1564 CFArrayAppendValue(mutableAccessGroups
, accessGroup
);
1565 accessGroups
= mutableAccessGroups
;
1567 CFReleaseNull(accessGroup
);
1571 if (CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), CFSTR("*"))) {
1572 /* Having the special accessGroup "*" allows access to all accessGroups. */
1573 accessGroups
= NULL
;
1577 Query
*q
= query_create_with_limit(query
, client
->musr
, 1, error
);
1579 CFStringRef agrp
= CFDictionaryGetValue(q
->q_item
, kSecAttrAccessGroup
);
1580 if (agrp
&& accessGroupsAllows(accessGroups
, agrp
, client
)) {
1581 // TODO: Return an error if agrp is not NULL and accessGroupsAllows() fails above.
1582 const void *val
= agrp
;
1583 accessGroups
= CFArrayCreate(0, &val
, 1, &kCFTypeArrayCallBacks
);
1585 CFRetainSafe(accessGroups
);
1588 #if TARGET_OS_IPHONE
1589 if (q
->q_sync_bubble
&& client
->inMultiUser
) {
1590 CFReleaseNull(q
->q_musrView
);
1591 q
->q_musrView
= SecMUSRCreateSyncBubbleUserUUID(q
->q_sync_bubble
);
1592 } else if (client
->inMultiUser
&& client
->isNetworkExtension
) {
1593 CFReleaseNull(q
->q_musrView
);
1594 q
->q_musrView
= SecMUSRCreateBothUserAndSystemUUID(client
->uid
);
1595 } else if (q
->q_system_keychain
&& client
->inMultiUser
) {
1596 CFReleaseNull(q
->q_musrView
);
1597 q
->q_musrView
= SecMUSRCopySystemKeychainUUID();
1599 q
->q_system_keychain
= false;
1603 query_set_caller_access_groups(q
, accessGroups
);
1605 /* Sanity check the query. */
1606 if (q
->q_system_keychain
&& !client
->allowSystemKeychain
) {
1607 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for system keychain"));
1608 } else if (q
->q_sync_bubble
&& !client
->allowSyncBubbleKeychain
) {
1609 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for syncbubble keychain"));
1610 } else if (q
->q_system_keychain
&& q
->q_sync_bubble
) {
1611 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("can't do both system and syncbubble keychain"));
1612 } else if (q
->q_use_item_list
) {
1613 ok
= SecError(errSecUseItemListUnsupported
, error
, CFSTR("use item list unsupported"));
1614 } else if (q
->q_match_issuer
&& ((q
->q_class
!= cert_class()) &&
1615 (q
->q_class
!= identity_class()))) {
1616 ok
= SecError(errSecUnsupportedOperation
, error
, CFSTR("unsupported match attribute"));
1617 } else if (q
->q_match_policy
&& ((q
->q_class
!= cert_class()) &&
1618 (q
->q_class
!= identity_class()))) {
1619 ok
= SecError(errSecUnsupportedOperation
, error
, CFSTR("unsupported kSecMatchPolicy attribute"));
1620 } else if (q
->q_return_type
!= 0 && result
== NULL
) {
1621 ok
= SecError(errSecReturnMissingPointer
, error
, CFSTR("missing pointer"));
1622 } else if (!q
->q_error
) {
1623 ok
= kc_with_dbt(false, error
, ^(SecDbConnectionRef dbt
) {
1624 return s3dl_copy_matching(dbt
, q
, result
, accessGroups
, error
);
1628 CFReleaseSafe(accessGroups
);
1629 if (!query_destroy(q
, error
))
1632 CFReleaseNull(mutableAccessGroups
);
1634 SecSignpostStop(SecSignpostSecItemCopyMatching
);
1640 _SecItemCopyMatching(CFDictionaryRef query
, SecurityClient
*client
, CFTypeRef
*result
, CFErrorRef
*error
) {
1641 return SecItemServerCopyMatching(query
, result
, client
, error
);
1644 #if TARGET_OS_IPHONE
1646 SecItemSynchronizable(CFDictionaryRef query
)
1648 bool result
= false;
1649 CFTypeRef value
= CFDictionaryGetValue(query
, kSecAttrSynchronizable
);
1650 if (isBoolean(value
))
1651 return CFBooleanGetValue(value
);
1652 else if (isNumber(value
)) {
1654 (void)CFNumberGetValue(value
, kCFNumberSInt32Type
, &number
);
1663 SecurityClientCopyWritableAccessGroups(SecurityClient
*client
) {
1664 if (client
== NULL
|| client
->accessGroups
== NULL
) {
1667 CFIndex count
= CFArrayGetCount(client
->accessGroups
);
1668 if (CFArrayContainsValue(client
->accessGroups
, CFRangeMake(0, count
), kSecAttrAccessGroupToken
)) {
1669 CFMutableArrayRef writableGroups
= CFArrayCreateMutableCopy(kCFAllocatorDefault
, 0, client
->accessGroups
);
1670 CFArrayRemoveAllValue(writableGroups
, kSecAttrAccessGroupToken
);
1671 return writableGroups
;
1673 return CFRetainSafe(client
->accessGroups
);
1678 /* AUDIT[securityd](done):
1679 attributes (ok) is a caller provided dictionary, only its cf type has
1683 _SecItemAdd(CFDictionaryRef attributes
, SecurityClient
*client
, CFTypeRef
*result
, CFErrorRef
*error
)
1685 CFArrayRef accessGroups
= SecurityClientCopyWritableAccessGroups(client
);
1689 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
))) {
1690 CFReleaseNull(accessGroups
);
1691 return SecEntitlementError(errSecMissingEntitlement
, error
);
1694 SecSignpostStart(SecSignpostSecItemAdd
);
1696 Query
*q
= query_create_with_limit(attributes
, client
->musr
, 0, error
);
1698 /* Access group sanity checking. */
1699 CFStringRef agrp
= (CFStringRef
)CFDictionaryGetValue(attributes
,
1700 kSecAttrAccessGroup
);
1702 /* Having the special accessGroup "*" allows access to all accessGroups. */
1703 if (CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), CFSTR("*")))
1704 CFReleaseNull(accessGroups
);
1707 /* The user specified an explicit access group, validate it. */
1708 if (!accessGroupsAllows(accessGroups
, agrp
, client
))
1709 ok
= SecError(errSecMissingEntitlement
, error
,
1710 CFSTR("explicit accessGroup %@ not in client access %@"), agrp
, accessGroups
);
1712 agrp
= (CFStringRef
)CFArrayGetValueAtIndex(client
->accessGroups
, 0);
1714 /* We are using an implicit access group, add it as if the user
1715 specified it as an attribute. */
1716 query_add_attribute(kSecAttrAccessGroup
, agrp
, q
);
1719 if (SecPLShouldLogRegisteredEvent(CFSTR("SecItem"))) {
1720 CFDictionaryRef dict
= CFDictionaryCreateForCFTypes(NULL
, CFSTR("operation"), CFSTR("add"), CFSTR("AccessGroup"), agrp
, NULL
);
1722 SecPLLogRegisteredEvent(CFSTR("SecItem"), dict
);
1726 #if TARGET_OS_IPHONE
1727 if (q
->q_system_keychain
&& client
->inMultiUser
) {
1728 CFReleaseNull(q
->q_musrView
);
1729 q
->q_musrView
= SecMUSRCopySystemKeychainUUID();
1731 q
->q_system_keychain
= false;
1733 query_add_attribute_with_desc(&v8musr
, q
->q_musrView
, q
);
1737 query_ensure_access_control(q
, agrp
);
1740 void (^add_sync_callback
)(bool, CFErrorRef
) = CFDictionaryGetValue(attributes
, CFSTR("f_ckkscallback"));
1741 if(add_sync_callback
) {
1742 // The existence of this callback indicates that we need a predictable UUID for this item.
1743 q
->q_uuid_from_primary_key
= true;
1744 q
->q_add_sync_callback
= add_sync_callback
;
1748 if (q
->q_system_keychain
&& !client
->allowSystemKeychain
) {
1749 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for system keychain"));
1750 } else if (q
->q_sync_bubble
&& !client
->allowSyncBubbleKeychain
) {
1751 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for syncbubble keychain"));
1752 #if TARGET_OS_IPHONE
1753 } else if (q
->q_system_keychain
&& SecItemSynchronizable(attributes
) && !client
->inMultiUser
) {
1754 ok
= SecError(errSecInvalidKey
, error
, CFSTR("Can't store system keychain and synchronizable"));
1756 } else if (q
->q_row_id
|| q
->q_token_object_id
) {
1757 ok
= SecError(errSecValuePersistentRefUnsupported
, error
, CFSTR("q_row_id")); // TODO: better error string
1758 } else if (!q
->q_error
) {
1759 ok
= kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
){
1760 return kc_transaction(dbt
, error
, ^{
1761 query_pre_add(q
, true);
1762 return s3dl_query_add(dbt
, q
, result
, error
);
1767 ok
= query_notify_and_destroy(q
, ok
, error
);
1772 SecSignpostStop(SecSignpostSecItemAdd
);
1774 CFReleaseNull(accessGroups
);
1778 /* AUDIT[securityd](done):
1779 query (ok) and attributesToUpdate (ok) are a caller provided dictionaries,
1780 only their cf types have been checked.
1783 _SecItemUpdate(CFDictionaryRef query
, CFDictionaryRef attributesToUpdate
,
1784 SecurityClient
*client
, CFErrorRef
*error
)
1786 CFArrayRef accessGroups
= SecurityClientCopyWritableAccessGroups(client
);
1789 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
))) {
1790 CFReleaseNull(accessGroups
);
1791 return SecEntitlementError(errSecMissingEntitlement
, error
);
1794 SecSignpostStart(SecSignpostSecItemUpdate
);
1796 if (SecPLShouldLogRegisteredEvent(CFSTR("SecItem"))) {
1797 CFTypeRef agrp
= CFArrayGetValueAtIndex(accessGroups
, 0);
1798 CFDictionaryRef dict
= CFDictionaryCreateForCFTypes(NULL
, CFSTR("operation"), CFSTR("update"), CFSTR("AccessGroup"), agrp
, NULL
);
1800 SecPLLogRegisteredEvent(CFSTR("SecItem"), dict
);
1805 if (CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), CFSTR("*"))) {
1806 /* Having the special accessGroup "*" allows access to all accessGroups. */
1807 CFReleaseNull(accessGroups
);
1811 Query
*q
= query_create_with_limit(query
, client
->musr
, kSecMatchUnlimited
, error
);
1816 #if TARGET_OS_IPHONE
1817 if (q
->q_system_keychain
&& client
->inMultiUser
) {
1818 CFReleaseNull(q
->q_musrView
);
1819 q
->q_musrView
= SecMUSRCopySystemKeychainUUID();
1821 q
->q_system_keychain
= false;
1825 /* Sanity check the query. */
1826 query_set_caller_access_groups(q
, accessGroups
);
1827 if (q
->q_system_keychain
&& !client
->allowSystemKeychain
) {
1828 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for system keychain"));
1829 } else if (q
->q_sync_bubble
&& !client
->allowSyncBubbleKeychain
) {
1830 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for syncbubble keychain"));
1831 #if TARGET_OS_IPHONE
1832 } else if (q
->q_system_keychain
&& SecItemSynchronizable(attributesToUpdate
) && !client
->inMultiUser
) {
1833 ok
= SecError(errSecInvalidKey
, error
, CFSTR("Can't update an system keychain item with synchronizable"));
1835 } else if (q
->q_use_item_list
) {
1836 ok
= SecError(errSecUseItemListUnsupported
, error
, CFSTR("use item list not supported"));
1837 } else if (q
->q_return_type
& kSecReturnDataMask
) {
1838 /* Update doesn't return anything so don't ask for it. */
1839 ok
= SecError(errSecReturnDataUnsupported
, error
, CFSTR("return data not supported by update"));
1840 } else if (q
->q_return_type
& kSecReturnAttributesMask
) {
1841 ok
= SecError(errSecReturnAttributesUnsupported
, error
, CFSTR("return attributes not supported by update"));
1842 } else if (q
->q_return_type
& kSecReturnRefMask
) {
1843 ok
= SecError(errSecReturnRefUnsupported
, error
, CFSTR("return ref not supported by update"));
1844 } else if (q
->q_return_type
& kSecReturnPersistentRefMask
) {
1845 ok
= SecError(errSecReturnPersistentRefUnsupported
, error
, CFSTR("return persistent ref not supported by update"));
1847 /* Access group sanity checking. */
1848 CFStringRef agrp
= (CFStringRef
)CFDictionaryGetValue(attributesToUpdate
,
1849 kSecAttrAccessGroup
);
1851 /* The user is attempting to modify the access group column,
1852 validate it to make sure the new value is allowable. */
1853 if (!accessGroupsAllows(accessGroups
, agrp
, client
)) {
1854 ok
= SecError(errSecNoAccessForItem
, error
, CFSTR("accessGroup %@ not in %@"), agrp
, accessGroups
);
1860 ok
= kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
1861 return kc_transaction(dbt
, error
, ^{
1862 return s3dl_query_update(dbt
, q
, attributesToUpdate
, accessGroups
, error
);
1867 ok
= query_notify_and_destroy(q
, ok
, error
);
1870 SecSignpostStop(SecSignpostSecItemUpdate
);
1872 CFReleaseNull(accessGroups
);
1877 /* AUDIT[securityd](done):
1878 query (ok) is a caller provided dictionary, only its cf type has been checked.
1881 _SecItemDelete(CFDictionaryRef query
, SecurityClient
*client
, CFErrorRef
*error
)
1883 CFArrayRef accessGroups
= SecurityClientCopyWritableAccessGroups(client
);
1886 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
))) {
1887 CFReleaseNull(accessGroups
);
1888 return SecEntitlementError(errSecMissingEntitlement
, error
);
1891 SecSignpostStart(SecSignpostSecItemDelete
);
1893 if (SecPLShouldLogRegisteredEvent(CFSTR("SecItem"))) {
1894 CFTypeRef agrp
= CFArrayGetValueAtIndex(accessGroups
, 0);
1895 CFDictionaryRef dict
= CFDictionaryCreateForCFTypes(NULL
, CFSTR("operation"), CFSTR("delete"), CFSTR("AccessGroup"), agrp
, NULL
);
1897 SecPLLogRegisteredEvent(CFSTR("SecItem"), dict
);
1902 if (CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), CFSTR("*"))) {
1903 /* Having the special accessGroup "*" allows access to all accessGroups. */
1904 CFReleaseNull(accessGroups
);
1907 Query
*q
= query_create_with_limit(query
, client
->musr
, kSecMatchUnlimited
, error
);
1910 #if TARGET_OS_IPHONE
1911 if (q
->q_system_keychain
&& client
->inMultiUser
) {
1912 CFReleaseNull(q
->q_musrView
);
1913 q
->q_musrView
= SecMUSRCopySystemKeychainUUID();
1915 q
->q_system_keychain
= false;
1919 query_set_caller_access_groups(q
, accessGroups
);
1920 /* Sanity check the query. */
1921 if (q
->q_system_keychain
&& !client
->allowSystemKeychain
) {
1922 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for system keychain"));
1923 } else if (q
->q_sync_bubble
&& !client
->allowSyncBubbleKeychain
) {
1924 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for syncbubble keychain"));
1925 } else if (q
->q_limit
!= kSecMatchUnlimited
) {
1926 ok
= SecError(errSecMatchLimitUnsupported
, error
, CFSTR("match limit not supported by delete"));
1927 } else if (query_match_count(q
) != 0) {
1928 ok
= SecError(errSecItemMatchUnsupported
, error
, CFSTR("match not supported by delete"));
1929 } else if (q
->q_ref
) {
1930 ok
= SecError(errSecValueRefUnsupported
, error
, CFSTR("value ref not supported by delete"));
1931 } else if (q
->q_row_id
&& query_attr_count(q
)) {
1932 ok
= SecError(errSecItemIllegalQuery
, error
, CFSTR("rowid and other attributes are mutually exclusive"));
1933 } else if (q
->q_token_object_id
&& query_attr_count(q
) != 1) {
1934 ok
= SecError(errSecItemIllegalQuery
, error
, CFSTR("token persistent ref and other attributes are mutually exclusive"));
1936 ok
= kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
1937 return kc_transaction(dbt
, error
, ^{
1938 return s3dl_query_delete(dbt
, q
, accessGroups
, error
);
1942 ok
= query_notify_and_destroy(q
, ok
, error
);
1947 SecSignpostStop(SecSignpostSecItemDelete
);
1949 CFReleaseNull(accessGroups
);
1953 static bool SecItemDeleteTokenItems(SecDbConnectionRef dbt
, CFTypeRef classToDelete
, CFTypeRef tokenID
, CFArrayRef accessGroups
, SecurityClient
*client
, CFErrorRef
*error
) {
1954 CFTypeRef keys
[] = { kSecClass
, kSecAttrTokenID
};
1955 CFTypeRef values
[] = { classToDelete
, tokenID
};
1957 CFDictionaryRef query
= CFDictionaryCreate(kCFAllocatorDefault
, keys
, values
, 2, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1958 Query
*q
= query_create_with_limit(query
, client
->musr
, kSecMatchUnlimited
, error
);
1962 query_set_caller_access_groups(q
, accessGroups
);
1963 ok
= s3dl_query_delete(dbt
, q
, accessGroups
, error
);
1964 ok
= query_notify_and_destroy(q
, ok
, error
);
1972 static bool SecItemAddTokenItem(SecDbConnectionRef dbt
, CFDictionaryRef attributes
, CFArrayRef accessGroups
, SecurityClient
*client
, CFErrorRef
*error
) {
1974 Query
*q
= query_create_with_limit(attributes
, client
->musr
, 0, error
);
1976 CFStringRef agrp
= kSecAttrAccessGroupToken
;
1977 query_add_attribute(kSecAttrAccessGroup
, agrp
, q
);
1980 query_ensure_access_control(q
, agrp
);
1981 if (q
->q_system_keychain
&& !client
->allowSystemKeychain
) {
1982 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for system keychain"));
1983 } else if (q
->q_sync_bubble
&& !client
->allowSyncBubbleKeychain
) {
1984 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for syncbubble keychain"));
1985 } else if (q
->q_row_id
|| q
->q_token_object_id
) {
1986 ok
= SecError(errSecValuePersistentRefUnsupported
, error
, CFSTR("q_row_id")); // TODO: better error string
1987 } else if (!q
->q_error
) {
1988 query_pre_add(q
, true);
1989 ok
= s3dl_query_add(dbt
, q
, NULL
, error
);
1992 ok
= query_notify_and_destroy(q
, ok
, error
);
1999 bool _SecItemUpdateTokenItems(CFStringRef tokenID
, CFArrayRef items
, SecurityClient
*client
, CFErrorRef
*error
) {
2001 CFArrayRef accessGroups
= client
->accessGroups
;
2003 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
))) {
2004 return SecEntitlementError(errSecMissingEntitlement
, error
);
2007 ok
= kc_with_dbt(true, error
, ^bool (SecDbConnectionRef dbt
) {
2008 return kc_transaction(dbt
, error
, ^bool {
2010 const CFTypeRef classToDelete
[] = { kSecClassGenericPassword
, kSecClassInternetPassword
, kSecClassCertificate
, kSecClassKey
};
2011 for (size_t i
= 0; i
< sizeof(classToDelete
) / sizeof(classToDelete
[0]); ++i
) {
2012 SecItemDeleteTokenItems(dbt
, classToDelete
[i
], tokenID
, accessGroups
, client
, NULL
);
2015 for (CFIndex i
= 0; i
< CFArrayGetCount(items
); ++i
) {
2016 if (!SecItemAddTokenItem(dbt
, CFArrayGetValueAtIndex(items
, i
), accessGroups
, client
, error
))
2022 const CFTypeRef classToDelete
[] = { kSecClassGenericPassword
, kSecClassInternetPassword
, kSecClassCertificate
, kSecClassKey
};
2023 bool deleted
= true;
2024 for (size_t i
= 0; i
< sizeof(classToDelete
) / sizeof(classToDelete
[0]); ++i
) {
2025 if (!SecItemDeleteTokenItems(dbt
, classToDelete
[i
], tokenID
, accessGroups
, client
, error
) && error
&& CFErrorGetCode(*error
) != errSecItemNotFound
) {
2029 else if (error
&& *error
) {
2030 CFReleaseNull(*error
);
2041 static bool deleteNonSysboundItemsForItemClass(SecDbConnectionRef dbt
, SecDbClass
const* class, CFErrorRef
* error
) {
2042 CFMutableDictionaryRef query
= CFDictionaryCreateMutableForCFTypes(NULL
);
2043 CFDictionaryAddValue(query
, kSecMatchLimit
, kSecMatchLimitAll
);
2045 __block CFErrorRef localError
= NULL
;
2046 SecDbQueryRef q
= query_create(class, NULL
, query
, &localError
);
2047 if (q
== NULL
) { // illegal query or out of memory
2048 secerror("SecItemServerDeleteAll: aborting because failed to initialize Query: %@", localError
);
2051 SecDbItemSelect(q
, dbt
, &localError
, ^bool(const SecDbAttr
*attr
) {
2052 return (attr
->flags
& kSecDbInFlag
) && !CFEqual(attr
->name
, CFSTR("data"));
2053 }, NULL
, NULL
, NULL
,
2054 ^(SecDbItemRef item
, bool *stop
) {
2055 if (!SecItemIsSystemBound(item
->attributes
, class, false) &&
2056 !CFEqual(CFDictionaryGetValue(item
->attributes
, kSecAttrAccessGroup
), CFSTR("com.apple.bluetooth")))
2058 SecDbItemDelete(item
, dbt
, kCFBooleanFalse
, &localError
);
2061 query_destroy(q
, &localError
);
2065 CFReleaseNull(*error
);
2066 *error
= localError
;
2068 CFReleaseNull(localError
);
2075 // Delete all the items except sysbound ones because horrible things happen if you do, like bluetooth devices unpairing
2077 SecItemServerDeleteAll(CFErrorRef
*error
) {
2078 secerror("SecItemServerDeleteAll");
2079 return kc_with_dbt(true, error
, ^bool (SecDbConnectionRef dbt
) {
2080 return (kc_transaction(dbt
, error
, ^bool {
2082 ok
&= SecDbExec(dbt
, CFSTR("DELETE FROM genp WHERE sync=1;"), error
);
2083 ok
&= SecDbExec(dbt
, CFSTR("DELETE FROM inet WHERE sync=1;"), error
);
2084 ok
&= SecDbExec(dbt
, CFSTR("DELETE FROM cert WHERE sync=1;"), error
);
2085 ok
&= SecDbExec(dbt
, CFSTR("DELETE FROM keys WHERE sync=1;"), error
);
2087 ok
&= deleteNonSysboundItemsForItemClass(dbt
, genp_class(), error
);
2088 ok
&= deleteNonSysboundItemsForItemClass(dbt
, inet_class(), error
);
2089 ok
&= deleteNonSysboundItemsForItemClass(dbt
, cert_class(), error
);
2090 ok
&= deleteNonSysboundItemsForItemClass(dbt
, keys_class(), error
);
2093 }) && SecDbExec(dbt
, CFSTR("VACUUM;"), error
));
2098 _SecItemDeleteAll(CFErrorRef
*error
) {
2099 return SecItemServerDeleteAll(error
);
2103 _SecItemServerDeleteAllWithAccessGroups(CFArrayRef accessGroups
, SecurityClient
*client
, CFErrorRef
*error
)
2105 __block
bool ok
= true;
2106 static dispatch_once_t onceToken
;
2107 static CFSetRef illegalAccessGroups
= NULL
;
2109 dispatch_once(&onceToken
, ^{
2110 const CFStringRef values
[] = {
2113 CFSTR("com.apple.security.sos"),
2114 CFSTR("lockdown-identities"),
2116 illegalAccessGroups
= CFSetCreate(NULL
, (const void **)values
, sizeof(values
)/sizeof(values
[0]), &kCFTypeSetCallBacks
);
2119 static CFTypeRef qclasses
[] = {
2125 // strange construction needed for schema indirection
2126 static dispatch_once_t qclassesOnceToken
;
2127 dispatch_once(&qclassesOnceToken
, ^{
2128 qclasses
[0] = inet_class();
2129 qclasses
[1] = genp_class();
2130 qclasses
[2] = keys_class();
2131 qclasses
[3] = cert_class();
2134 require_action_quiet(isArray(accessGroups
), fail
,
2136 SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType
, sSecXPCErrorDomain
, NULL
, error
, NULL
, CFSTR("accessGroups not CFArray, got %@"), accessGroups
));
2138 // TODO: whitelist instead? look for dev IDs like 7123498YQX.com.somedev.app
2140 require_action(CFArrayGetCount(accessGroups
) != 0, fail
,
2142 SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType
, sSecXPCErrorDomain
, NULL
, error
, NULL
, CFSTR("accessGroups e empty")));
2145 // Pre-check accessGroups for prohibited values
2146 CFArrayForEach(accessGroups
, ^(const void *value
) {
2147 CFStringRef agrp
= (CFStringRef
)value
;
2149 if (!isString(agrp
)) {
2150 SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType
, sSecXPCErrorDomain
, NULL
, error
, NULL
,
2151 CFSTR("access not a string: %@"), agrp
);
2153 } else if (CFSetContainsValue(illegalAccessGroups
, agrp
)) {
2154 SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType
, sSecXPCErrorDomain
, NULL
, error
, NULL
,
2155 CFSTR("illegal access group: %@"), accessGroups
);
2161 ok
= kc_with_dbt(true, error
, ^bool(SecDbConnectionRef dbt
) {
2162 return kc_transaction(dbt
, error
, ^bool {
2163 CFErrorRef localError
= NULL
;
2167 for (n
= 0; n
< sizeof(qclasses
)/sizeof(qclasses
[0]) && ok1
; n
++) {
2170 q
= query_create(qclasses
[n
], client
->musr
, NULL
, error
);
2173 (void)s3dl_query_delete(dbt
, q
, accessGroups
, &localError
);
2175 query_destroy(q
, error
);
2176 CFReleaseNull(localError
);
2179 }) && SecDbExec(dbt
, CFSTR("VACUUM"), error
);
2188 // MARK: Shared web credentials
2190 #if TARGET_OS_IOS && !TARGET_OS_BRIDGE
2193 #define SEC_CONST_DECL(k,v) const CFStringRef k = CFSTR(v);
2195 SEC_CONST_DECL (kSecSafariAccessGroup
, "com.apple.cfnetwork");
2196 SEC_CONST_DECL (kSecSafariDefaultComment
, "default");
2197 SEC_CONST_DECL (kSecSafariPasswordsNotSaved
, "Passwords not saved");
2198 SEC_CONST_DECL (kSecSharedCredentialUrlScheme
, "https://");
2199 SEC_CONST_DECL (kSecSharedWebCredentialsService
, "webcredentials");
2201 #if !TARGET_IPHONE_SIMULATOR
2203 _SecAppDomainApprovalStatus(CFStringRef appID
, CFStringRef fqdn
, CFErrorRef
*error
)
2205 __block SWCFlags flags
= kSWCFlags_None
;
2208 dispatch_semaphore_t semaphore
= dispatch_semaphore_create(0);
2209 if (semaphore
== NULL
)
2212 status
= SWCCheckService(kSecSharedWebCredentialsService
, appID
, fqdn
, ^void (OSStatus inStatus
, SWCFlags inFlags
, CFDictionaryRef inDetails
)
2214 if (inStatus
== 0) {
2217 secerror("SWCCheckService failed with %d", (int)inStatus
);
2219 dispatch_semaphore_signal(semaphore
);
2223 dispatch_semaphore_wait(semaphore
, DISPATCH_TIME_FOREVER
);
2225 secerror("SWCCheckService: failed to queue");
2227 dispatch_release(semaphore
);
2230 if (!(flags
& kSWCFlag_SiteApproved
)) {
2231 if (flags
& kSWCFlag_Pending
) {
2232 SecError(errSecAuthFailed
, error
, CFSTR("Approval is pending for \"%@\", try later"), fqdn
);
2234 SecError(errSecAuthFailed
, error
, CFSTR("\"%@\" failed to approve \"%@\""), fqdn
, appID
);
2236 } else if (flags
& kSWCFlag_UserDenied
) {
2237 SecError(errSecAuthFailed
, error
, CFSTR("User denied access to \"%@\" by \"%@\""), fqdn
, appID
);
2244 _SecEntitlementContainsDomainForService(CFArrayRef domains
, CFStringRef domain
, CFStringRef service
)
2246 bool result
= false;
2247 CFIndex idx
, count
= (domains
) ? CFArrayGetCount(domains
) : (CFIndex
) 0;
2248 if (!count
|| !domain
|| !service
) {
2251 for (idx
=0; idx
< count
; idx
++) {
2252 CFStringRef str
= (CFStringRef
) CFArrayGetValueAtIndex(domains
, idx
);
2253 if (str
&& CFStringHasPrefix(str
, kSecSharedWebCredentialsService
)) {
2254 CFIndex prefix_len
= CFStringGetLength(kSecSharedWebCredentialsService
)+1;
2255 CFIndex substr_len
= CFStringGetLength(str
) - prefix_len
;
2256 CFRange range
= { prefix_len
, substr_len
};
2257 CFStringRef substr
= CFStringCreateWithSubstring(kCFAllocatorDefault
, str
, range
);
2258 if (substr
&& CFEqual(substr
, domain
)) {
2261 CFReleaseSafe(substr
);
2269 #endif /* !TARGET_OS_SIMULATOR */
2272 _SecAddNegativeWebCredential(SecurityClient
*client
, CFStringRef fqdn
, CFStringRef appID
, bool forSafari
)
2274 #if !TARGET_IPHONE_SIMULATOR
2275 bool result
= false;
2276 if (!fqdn
) { return result
; }
2278 // update our database
2279 CFRetainSafe(appID
);
2281 if (0 == SWCSetServiceFlags(kSecSharedWebCredentialsService
, appID
, fqdn
, kSWCFlag_ExternalMask
, kSWCFlag_UserDenied
,
2282 ^void(OSStatus inStatus
, SWCFlags inNewFlags
){
2283 CFReleaseSafe(appID
);
2284 CFReleaseSafe(fqdn
);
2289 else // didn't queue the block
2291 CFReleaseSafe(appID
);
2292 CFReleaseSafe(fqdn
);
2295 if (!forSafari
) { return result
; }
2297 // below this point: create a negative Safari web credential item
2299 CFMutableDictionaryRef attrs
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2300 if (!attrs
) { return result
; }
2302 CFErrorRef error
= NULL
;
2303 CFStringRef accessGroup
= CFSTR("*");
2304 SecurityClient swcclient
= {
2306 .accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&accessGroup
, 1, &kCFTypeArrayCallBacks
),
2307 .allowSystemKeychain
= false,
2308 .allowSyncBubbleKeychain
= false,
2309 .isNetworkExtension
= false,
2310 .musr
= client
->musr
,
2313 CFDictionaryAddValue(attrs
, kSecClass
, kSecClassInternetPassword
);
2314 CFDictionaryAddValue(attrs
, kSecAttrAccessGroup
, kSecSafariAccessGroup
);
2315 CFDictionaryAddValue(attrs
, kSecAttrAuthenticationType
, kSecAttrAuthenticationTypeHTMLForm
);
2316 CFDictionaryAddValue(attrs
, kSecAttrProtocol
, kSecAttrProtocolHTTPS
);
2317 CFDictionaryAddValue(attrs
, kSecAttrServer
, fqdn
);
2318 CFDictionaryAddValue(attrs
, kSecAttrSynchronizable
, kCFBooleanTrue
);
2320 (void)_SecItemDelete(attrs
, &swcclient
, &error
);
2321 CFReleaseNull(error
);
2323 CFDictionaryAddValue(attrs
, kSecAttrAccount
, kSecSafariPasswordsNotSaved
);
2324 CFDictionaryAddValue(attrs
, kSecAttrComment
, kSecSafariDefaultComment
);
2326 CFStringRef label
= CFStringCreateWithFormat(kCFAllocatorDefault
,
2327 NULL
, CFSTR("%@ (%@)"), fqdn
, kSecSafariPasswordsNotSaved
);
2329 CFDictionaryAddValue(attrs
, kSecAttrLabel
, label
);
2330 CFReleaseSafe(label
);
2334 CFDataRef data
= CFDataCreate(kCFAllocatorDefault
, &space
, 1);
2336 CFDictionarySetValue(attrs
, kSecValueData
, data
);
2337 CFReleaseSafe(data
);
2340 CFTypeRef addResult
= NULL
;
2341 result
= _SecItemAdd(attrs
, &swcclient
, &addResult
, &error
);
2343 CFReleaseSafe(addResult
);
2344 CFReleaseSafe(error
);
2345 CFReleaseSafe(attrs
);
2346 CFReleaseSafe(swcclient
.accessGroups
);
2354 /* Specialized version of SecItemAdd for shared web credentials */
2356 _SecAddSharedWebCredential(CFDictionaryRef attributes
,
2357 SecurityClient
*client
,
2358 const audit_token_t
*clientAuditToken
,
2365 SecurityClient swcclient
= {};
2367 CFStringRef fqdn
= CFRetainSafe(CFDictionaryGetValue(attributes
, kSecAttrServer
));
2368 CFStringRef account
= CFDictionaryGetValue(attributes
, kSecAttrAccount
);
2369 #if TARGET_OS_IOS && !TARGET_OS_BRIDGE
2370 CFStringRef password
= CFDictionaryGetValue(attributes
, kSecSharedPassword
);
2372 CFStringRef password
= CFDictionaryGetValue(attributes
, CFSTR("spwd"));
2374 CFStringRef accessGroup
= CFSTR("*");
2375 CFMutableDictionaryRef query
= NULL
, attrs
= NULL
;
2379 // check autofill enabled status
2380 if (!swca_autofill_enabled(clientAuditToken
)) {
2381 SecError(errSecBadReq
, error
, CFSTR("Autofill is not enabled in Safari settings"));
2385 // parse fqdn with CFURL here, since it could be specified as domain:port
2387 CFStringRef urlStr
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%@%@"), kSecSharedCredentialUrlScheme
, fqdn
);
2389 CFURLRef url
= CFURLCreateWithString(kCFAllocatorDefault
, urlStr
, nil
);
2391 CFStringRef hostname
= CFURLCopyHostName(url
);
2393 CFReleaseSafe(fqdn
);
2395 port
= CFURLGetPortNumber(url
);
2399 CFReleaseSafe(urlStr
);
2404 SecError(errSecParam
, error
, CFSTR("No account provided"));
2408 SecError(errSecParam
, error
, CFSTR("No domain provided"));
2412 #if TARGET_IPHONE_SIMULATOR
2413 secerror("app/site association entitlements not checked in Simulator");
2415 OSStatus status
= errSecMissingEntitlement
;
2416 // validate that fqdn is part of caller's shared credential domains entitlement
2418 SecError(status
, error
, CFSTR("Missing application-identifier entitlement"));
2421 if (_SecEntitlementContainsDomainForService(domains
, fqdn
, kSecSharedWebCredentialsService
)) {
2422 status
= errSecSuccess
;
2424 if (errSecSuccess
!= status
) {
2425 CFStringRef msg
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
,
2426 CFSTR("%@ not found in %@ entitlement"), fqdn
, kSecEntitlementAssociatedDomains
);
2428 msg
= CFRetain(CFSTR("Requested domain not found in entitlement"));
2430 SecError(status
, error
, CFSTR("%@"), msg
);
2436 #if TARGET_IPHONE_SIMULATOR
2437 secerror("Ignoring app/site approval state in the Simulator.");
2439 // get approval status for this app/domain pair
2440 SWCFlags flags
= _SecAppDomainApprovalStatus(appID
, fqdn
, error
);
2441 if (!(flags
& kSWCFlag_SiteApproved
)) {
2446 // give ourselves access to see matching items for kSecSafariAccessGroup
2447 swcclient
.task
= NULL
;
2448 swcclient
.accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&accessGroup
, 1, &kCFTypeArrayCallBacks
);
2449 swcclient
.allowSystemKeychain
= false;
2450 swcclient
.musr
= client
->musr
;
2451 swcclient
.allowSystemKeychain
= false;
2452 swcclient
.allowSyncBubbleKeychain
= false;
2453 swcclient
.isNetworkExtension
= false;
2456 // create lookup query
2457 query
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2459 SecError(errSecAllocate
, error
, CFSTR("Unable to create query dictionary"));
2462 CFDictionaryAddValue(query
, kSecClass
, kSecClassInternetPassword
);
2463 CFDictionaryAddValue(query
, kSecAttrAccessGroup
, kSecSafariAccessGroup
);
2464 CFDictionaryAddValue(query
, kSecAttrAuthenticationType
, kSecAttrAuthenticationTypeHTMLForm
);
2465 CFDictionaryAddValue(query
, kSecAttrServer
, fqdn
);
2466 CFDictionaryAddValue(query
, kSecAttrSynchronizable
, kCFBooleanTrue
);
2468 // check for presence of Safari's negative entry ('passwords not saved')
2469 CFDictionarySetValue(query
, kSecAttrAccount
, kSecSafariPasswordsNotSaved
);
2470 ok
= _SecItemCopyMatching(query
, &swcclient
, result
, error
);
2471 if(result
) CFReleaseNull(*result
);
2472 if (error
) CFReleaseNull(*error
);
2474 SecError(errSecDuplicateItem
, error
, CFSTR("Item already exists for this server"));
2478 // now use the provided account (and optional port number, if one was present)
2479 CFDictionarySetValue(query
, kSecAttrAccount
, account
);
2480 if (port
< -1 || port
> 0) {
2481 SInt16 portValueShort
= (port
& 0xFFFF);
2482 CFNumberRef portNumber
= CFNumberCreate(NULL
, kCFNumberSInt16Type
, &portValueShort
);
2483 CFDictionaryAddValue(query
, kSecAttrPort
, portNumber
);
2484 CFReleaseSafe(portNumber
);
2487 // look up existing password
2488 CFDictionaryAddValue(query
, kSecReturnData
, kCFBooleanTrue
);
2489 bool matched
= _SecItemCopyMatching(query
, &swcclient
, result
, error
);
2490 CFDictionaryRemoveValue(query
, kSecReturnData
);
2492 // found it, so this becomes either an "update password" or "delete password" operation
2493 bool update
= (password
!= NULL
);
2495 attrs
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2496 CFDataRef credential
= CFStringCreateExternalRepresentation(kCFAllocatorDefault
, password
, kCFStringEncodingUTF8
, 0);
2497 CFDictionaryAddValue(attrs
, kSecValueData
, credential
);
2498 bool samePassword
= result
&& *result
&& CFEqual(*result
, credential
);
2499 CFReleaseSafe(credential
);
2500 CFDictionaryAddValue(attrs
, kSecAttrComment
, kSecSafariDefaultComment
);
2502 ok
= samePassword
|| swca_confirm_operation(swca_update_request_id
, clientAuditToken
, query
, error
,
2503 ^void (CFStringRef fqdn
) { _SecAddNegativeWebCredential(client
, fqdn
, appID
, false); });
2505 ok
= _SecItemUpdate(query
, attrs
, &swcclient
, error
);
2509 // confirm the delete
2510 // (per rdar://16676288 we always prompt, even if there was prior user approval)
2511 ok
= /*approved ||*/ swca_confirm_operation(swca_delete_request_id
, clientAuditToken
, query
, error
,
2512 ^void (CFStringRef fqdn
) { _SecAddNegativeWebCredential(client
, fqdn
, appID
, false); });
2514 ok
= _SecItemDelete(query
, &swcclient
, error
);
2518 if(result
) CFReleaseNull(*result
);
2519 if(error
) CFReleaseNull(*error
);
2523 if (result
) CFReleaseNull(*result
);
2524 if (error
) CFReleaseNull(*error
);
2526 // password does not exist, so prepare to add it
2528 // a NULL password value removes the existing credential. Since we didn't find it, this is a no-op.
2533 CFStringRef label
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%@ (%@)"), fqdn
, account
);
2535 CFDictionaryAddValue(query
, kSecAttrLabel
, label
);
2536 CFReleaseSafe(label
);
2538 // NOTE: we always expect to use HTTPS for web forms.
2539 CFDictionaryAddValue(query
, kSecAttrProtocol
, kSecAttrProtocolHTTPS
);
2541 CFDataRef credential
= CFStringCreateExternalRepresentation(kCFAllocatorDefault
, password
, kCFStringEncodingUTF8
, 0);
2542 CFDictionarySetValue(query
, kSecValueData
, credential
);
2543 CFReleaseSafe(credential
);
2544 CFDictionarySetValue(query
, kSecAttrComment
, kSecSafariDefaultComment
);
2546 CFReleaseSafe(swcclient
.accessGroups
);
2547 swcclient
.accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&kSecSafariAccessGroup
, 1, &kCFTypeArrayCallBacks
);
2549 // mark the item as created by this function
2550 const int32_t creator_value
= 'swca';
2551 CFNumberRef creator
= CFNumberCreate(kCFAllocatorDefault
, kCFNumberSInt32Type
, &creator_value
);
2553 CFDictionarySetValue(query
, kSecAttrCreator
, creator
);
2554 CFReleaseSafe(creator
);
2558 ok
= swca_confirm_operation(swca_add_request_id
, clientAuditToken
, query
, error
, ^void (CFStringRef fqdn
) {
2559 _SecAddNegativeWebCredential(client
, fqdn
, appID
, false);
2563 ok
= _SecItemAdd(query
, &swcclient
, result
, error
);
2567 CFReleaseSafe(attrs
);
2568 CFReleaseSafe(query
);
2569 CFReleaseSafe(swcclient
.accessGroups
);
2570 CFReleaseSafe(fqdn
);
2574 /* Specialized version of SecItemCopyMatching for shared web credentials */
2576 _SecCopySharedWebCredential(CFDictionaryRef query
,
2577 SecurityClient
*client
,
2578 const audit_token_t
*clientAuditToken
,
2584 CFMutableArrayRef credentials
= NULL
;
2585 CFMutableArrayRef foundItems
= NULL
;
2586 CFMutableArrayRef fqdns
= NULL
;
2587 CFStringRef fqdn
= NULL
;
2588 CFStringRef account
= NULL
;
2593 require_quiet(result
, cleanup
);
2594 credentials
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
2595 foundItems
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
2596 fqdns
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
2598 // give ourselves access to see matching items for kSecSafariAccessGroup
2599 CFStringRef accessGroup
= CFSTR("*");
2600 SecurityClient swcclient
= {
2602 .accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&accessGroup
, 1, &kCFTypeArrayCallBacks
),
2603 .allowSystemKeychain
= false,
2604 .allowSyncBubbleKeychain
= false,
2605 .isNetworkExtension
= false,
2606 .musr
= client
->musr
,
2609 // On input, the query dictionary contains optional fqdn and account entries.
2610 fqdn
= CFDictionaryGetValue(query
, kSecAttrServer
);
2611 account
= CFDictionaryGetValue(query
, kSecAttrAccount
);
2613 // Check autofill enabled status
2614 if (!swca_autofill_enabled(clientAuditToken
)) {
2615 SecError(errSecBadReq
, error
, CFSTR("Autofill is not enabled in Safari settings"));
2619 // Check fqdn; if NULL, add domains from caller's entitlement.
2621 CFArrayAppendValue(fqdns
, fqdn
);
2624 CFIndex idx
, count
= CFArrayGetCount(domains
);
2625 for (idx
=0; idx
< count
; idx
++) {
2626 CFStringRef str
= (CFStringRef
) CFArrayGetValueAtIndex(domains
, idx
);
2627 // Parse the entry for our service label prefix
2628 if (str
&& CFStringHasPrefix(str
, kSecSharedWebCredentialsService
)) {
2629 CFIndex prefix_len
= CFStringGetLength(kSecSharedWebCredentialsService
)+1;
2630 CFIndex substr_len
= CFStringGetLength(str
) - prefix_len
;
2631 CFRange range
= { prefix_len
, substr_len
};
2632 fqdn
= CFStringCreateWithSubstring(kCFAllocatorDefault
, str
, range
);
2634 CFArrayAppendValue(fqdns
, fqdn
);
2640 count
= CFArrayGetCount(fqdns
);
2642 SecError(errSecParam
, error
, CFSTR("No domain provided"));
2646 // Aggregate search results for each domain
2647 for (idx
= 0; idx
< count
; idx
++) {
2648 CFMutableArrayRef items
= NULL
;
2649 CFMutableDictionaryRef attrs
= NULL
;
2650 fqdn
= (CFStringRef
) CFArrayGetValueAtIndex(fqdns
, idx
);
2654 // Parse the fqdn for a possible port specifier.
2656 CFStringRef urlStr
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%@%@"), kSecSharedCredentialUrlScheme
, fqdn
);
2658 CFURLRef url
= CFURLCreateWithString(kCFAllocatorDefault
, urlStr
, nil
);
2660 CFStringRef hostname
= CFURLCopyHostName(url
);
2662 CFReleaseSafe(fqdn
);
2664 port
= CFURLGetPortNumber(url
);
2668 CFReleaseSafe(urlStr
);
2672 #if TARGET_IPHONE_SIMULATOR
2673 secerror("app/site association entitlements not checked in Simulator");
2675 OSStatus status
= errSecMissingEntitlement
;
2677 SecError(status
, error
, CFSTR("Missing application-identifier entitlement"));
2678 CFReleaseSafe(fqdn
);
2681 // validate that fqdn is part of caller's entitlement
2682 if (_SecEntitlementContainsDomainForService(domains
, fqdn
, kSecSharedWebCredentialsService
)) {
2683 status
= errSecSuccess
;
2685 if (errSecSuccess
!= status
) {
2686 CFStringRef msg
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
,
2687 CFSTR("%@ not found in %@ entitlement"), fqdn
, kSecEntitlementAssociatedDomains
);
2689 msg
= CFRetain(CFSTR("Requested domain not found in entitlement"));
2691 SecError(status
, error
, CFSTR("%@"), msg
);
2693 CFReleaseSafe(fqdn
);
2698 attrs
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2700 SecError(errSecAllocate
, error
, CFSTR("Unable to create query dictionary"));
2701 CFReleaseSafe(fqdn
);
2704 CFDictionaryAddValue(attrs
, kSecClass
, kSecClassInternetPassword
);
2705 CFDictionaryAddValue(attrs
, kSecAttrAccessGroup
, kSecSafariAccessGroup
);
2706 CFDictionaryAddValue(attrs
, kSecAttrProtocol
, kSecAttrProtocolHTTPS
);
2707 CFDictionaryAddValue(attrs
, kSecAttrAuthenticationType
, kSecAttrAuthenticationTypeHTMLForm
);
2708 CFDictionaryAddValue(attrs
, kSecAttrServer
, fqdn
);
2710 CFDictionaryAddValue(attrs
, kSecAttrAccount
, account
);
2712 if (port
< -1 || port
> 0) {
2713 SInt16 portValueShort
= (port
& 0xFFFF);
2714 CFNumberRef portNumber
= CFNumberCreate(NULL
, kCFNumberSInt16Type
, &portValueShort
);
2715 CFDictionaryAddValue(attrs
, kSecAttrPort
, portNumber
);
2716 CFReleaseSafe(portNumber
);
2718 CFDictionaryAddValue(attrs
, kSecAttrSynchronizable
, kCFBooleanTrue
);
2719 CFDictionaryAddValue(attrs
, kSecMatchLimit
, kSecMatchLimitAll
);
2720 CFDictionaryAddValue(attrs
, kSecReturnAttributes
, kCFBooleanTrue
);
2721 CFDictionaryAddValue(attrs
, kSecReturnData
, kCFBooleanTrue
);
2723 ok
= _SecItemCopyMatching(attrs
, &swcclient
, (CFTypeRef
*)&items
, error
);
2725 // ignore interim error since we have multiple domains to search
2726 CFReleaseNull(*error
);
2728 if (ok
&& items
&& CFGetTypeID(items
) == CFArrayGetTypeID()) {
2729 #if TARGET_IPHONE_SIMULATOR
2730 secerror("Ignoring app/site approval state in the Simulator.");
2731 bool approved
= true;
2733 // get approval status for this app/domain pair
2734 SWCFlags flags
= _SecAppDomainApprovalStatus(appID
, fqdn
, error
);
2736 // ignore interim error since we have multiple domains to check
2737 CFReleaseNull(*error
);
2739 bool approved
= (flags
& kSWCFlag_SiteApproved
);
2742 CFArrayAppendArray(foundItems
, items
, CFRangeMake(0, CFArrayGetCount(items
)));
2745 CFReleaseSafe(items
);
2746 CFReleaseSafe(attrs
);
2747 CFReleaseSafe(fqdn
);
2750 // If matching credentials are found, the credentials provided to the completionHandler
2751 // will be a CFArrayRef containing CFDictionaryRef entries. Each dictionary entry will
2752 // contain the following pairs (see Security/SecItem.h):
2753 // key: kSecAttrServer value: CFStringRef (the website)
2754 // key: kSecAttrAccount value: CFStringRef (the account)
2755 // key: kSecSharedPassword value: CFStringRef (the password)
2757 // key: kSecAttrPort value: CFNumberRef (the port number, if non-standard for https)
2759 count
= CFArrayGetCount(foundItems
);
2760 for (idx
= 0; idx
< count
; idx
++) {
2761 CFDictionaryRef dict
= (CFDictionaryRef
) CFArrayGetValueAtIndex(foundItems
, idx
);
2762 CFMutableDictionaryRef newdict
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2763 if (newdict
&& dict
&& CFGetTypeID(dict
) == CFDictionaryGetTypeID()) {
2764 CFStringRef srvr
= CFDictionaryGetValue(dict
, kSecAttrServer
);
2765 CFStringRef acct
= CFDictionaryGetValue(dict
, kSecAttrAccount
);
2766 CFNumberRef pnum
= CFDictionaryGetValue(dict
, kSecAttrPort
);
2767 CFStringRef icmt
= CFDictionaryGetValue(dict
, kSecAttrComment
);
2768 CFDataRef data
= CFDictionaryGetValue(dict
, kSecValueData
);
2770 CFDictionaryAddValue(newdict
, kSecAttrServer
, srvr
);
2773 CFDictionaryAddValue(newdict
, kSecAttrAccount
, acct
);
2777 if (CFNumberGetValue(pnum
, kCFNumberSInt16Type
, &pval
) &&
2778 (pval
< -1 || pval
> 0)) {
2779 CFDictionaryAddValue(newdict
, kSecAttrPort
, pnum
);
2783 CFStringRef password
= CFStringCreateFromExternalRepresentation(kCFAllocatorDefault
, data
, kCFStringEncodingUTF8
);
2785 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH && !TARGET_OS_TV
2786 CFDictionaryAddValue(newdict
, kSecSharedPassword
, password
);
2788 CFDictionaryAddValue(newdict
, CFSTR("spwd"), password
);
2790 CFReleaseSafe(password
);
2793 if (icmt
&& CFEqual(icmt
, kSecSafariDefaultComment
)) {
2794 CFArrayInsertValueAtIndex(credentials
, 0, newdict
);
2796 CFArrayAppendValue(credentials
, newdict
);
2799 CFReleaseSafe(newdict
);
2806 // create a new array of dictionaries (without the actual password) for picker UI
2807 count
= CFArrayGetCount(credentials
);
2808 CFMutableArrayRef items
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
2809 for (idx
= 0; idx
< count
; idx
++) {
2810 CFDictionaryRef dict
= (CFDictionaryRef
) CFArrayGetValueAtIndex(credentials
, idx
);
2811 CFMutableDictionaryRef newdict
= CFDictionaryCreateMutableCopy(kCFAllocatorDefault
, 0, dict
);
2812 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH && !TARGET_OS_TV
2813 CFDictionaryRemoveValue(newdict
, kSecSharedPassword
);
2815 CFDictionaryRemoveValue(newdict
, CFSTR("spwd"));
2817 CFArrayAppendValue(items
, newdict
);
2818 CFReleaseSafe(newdict
);
2821 // prompt user to select one of the dictionary items
2822 CFDictionaryRef selected
= swca_copy_selected_dictionary(swca_select_request_id
,
2823 clientAuditToken
, items
, error
);
2825 // find the matching item in our credentials array
2826 CFStringRef srvr
= CFDictionaryGetValue(selected
, kSecAttrServer
);
2827 CFStringRef acct
= CFDictionaryGetValue(selected
, kSecAttrAccount
);
2828 CFNumberRef pnum
= CFDictionaryGetValue(selected
, kSecAttrPort
);
2829 for (idx
= 0; idx
< count
; idx
++) {
2830 CFDictionaryRef dict
= (CFDictionaryRef
) CFArrayGetValueAtIndex(credentials
, idx
);
2831 CFStringRef srvr1
= CFDictionaryGetValue(dict
, kSecAttrServer
);
2832 CFStringRef acct1
= CFDictionaryGetValue(dict
, kSecAttrAccount
);
2833 CFNumberRef pnum1
= CFDictionaryGetValue(dict
, kSecAttrPort
);
2835 if (!srvr
|| !srvr1
|| !CFEqual(srvr
, srvr1
)) continue;
2836 if (!acct
|| !acct1
|| !CFEqual(acct
, acct1
)) continue;
2837 if ((pnum
&& pnum1
) && !CFEqual(pnum
, pnum1
)) continue;
2840 CFReleaseSafe(selected
);
2847 CFReleaseSafe(items
);
2848 CFArrayRemoveAllValues(credentials
);
2849 if (selected
&& ok
) {
2850 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
2851 fqdn
= CFDictionaryGetValue(selected
, kSecAttrServer
);
2853 CFArrayAppendValue(credentials
, selected
);
2857 #if TARGET_OS_IOS && !TARGET_OS_BRIDGE && !TARGET_IPHONE_SIMULATOR
2858 // register confirmation with database
2859 CFRetainSafe(appID
);
2861 if (0 != SWCSetServiceFlags(kSecSharedWebCredentialsService
,
2862 appID
, fqdn
, kSWCFlag_ExternalMask
, kSWCFlag_UserApproved
,
2863 ^void(OSStatus inStatus
, SWCFlags inNewFlags
){
2864 CFReleaseSafe(appID
);
2865 CFReleaseSafe(fqdn
);
2868 // we didn't queue the block
2869 CFReleaseSafe(appID
);
2870 CFReleaseSafe(fqdn
);
2874 CFReleaseSafe(selected
);
2876 else if (NULL
== *error
) {
2877 // found no items, and we haven't already filled in the error
2878 SecError(errSecItemNotFound
, error
, CFSTR("no matching items found"));
2883 CFArrayRemoveAllValues(credentials
);
2884 CFReleaseNull(credentials
);
2886 CFReleaseSafe(foundItems
);
2887 *result
= credentials
;
2888 CFReleaseSafe(swcclient
.accessGroups
);
2889 CFReleaseSafe(fqdns
);
2894 #endif /* TARGET_OS_IOS */
2898 // MARK: Keychain backup
2900 CF_RETURNS_RETAINED CFDataRef
2901 _SecServerKeychainCreateBackup(SecurityClient
*client
, CFDataRef keybag
, CFDataRef passcode
, bool emcs
, CFErrorRef
*error
) {
2902 __block CFDataRef backup
;
2903 kc_with_dbt(true, error
, ^bool (SecDbConnectionRef dbt
) {
2907 if (keybag
== NULL
&& passcode
== NULL
) {
2909 backup
= SecServerExportBackupableKeychain(dbt
, client
, KEYBAG_DEVICE
, backup_keybag_handle
, error
);
2910 #else /* !USE_KEYSTORE */
2912 SecError(errSecParam
, error
, CFSTR("Why are you doing this?"));
2914 #endif /* USE_KEYSTORE */
2916 backup
= SecServerKeychainCreateBackup(dbt
, client
, keybag
, passcode
, emcs
, error
);
2918 return (backup
!= NULL
);
2925 _SecServerKeychainRestore(CFDataRef backup
, SecurityClient
*client
, CFDataRef keybag
, CFDataRef passcode
, CFErrorRef
*error
) {
2926 if (backup
== NULL
|| keybag
== NULL
)
2927 return SecError(errSecParam
, error
, CFSTR("backup or keybag missing"));
2929 __block
bool ok
= true;
2930 ok
&= kc_with_dbt(true, error
, ^bool (SecDbConnectionRef dbconn
) {
2931 return SecServerKeychainRestore(dbconn
, client
, backup
, keybag
, passcode
, error
);
2935 SecKeychainChanged();
2942 _SecServerBackupCopyUUID(CFDataRef data
, CFErrorRef
*error
)
2944 CFStringRef uuid
= NULL
;
2945 CFDictionaryRef backup
;
2947 backup
= CFPropertyListCreateWithData(kCFAllocatorDefault
, data
,
2948 kCFPropertyListImmutable
, NULL
,
2950 if (isDictionary(backup
)) {
2951 uuid
= SecServerBackupGetKeybagUUID(backup
, error
);
2955 CFReleaseNull(backup
);
2963 // MARK: SecItemDataSource
2965 // Make sure to call this before any writes to the keychain, so that we fire
2966 // up the engines to monitor manifest changes.
2967 SOSDataSourceFactoryRef
SecItemDataSourceFactoryGetDefault(void) {
2968 return SecItemDataSourceFactoryGetShared(kc_dbhandle(NULL
));
2971 /* AUDIT[securityd]:
2972 args_in (ok) is a caller provided, CFDictionaryRef.
2975 CF_RETURNS_RETAINED CFArrayRef
2976 _SecServerKeychainSyncUpdateMessage(CFDictionaryRef updates
, CFErrorRef
*error
) {
2977 // This never fails, trust us!
2978 return SOSCCHandleUpdateMessage(updates
);
2982 // Truthiness in the cloud backup/restore support.
2985 static CFDictionaryRef
2986 _SecServerCopyTruthInTheCloud(CFDataRef keybag
, CFDataRef password
,
2987 CFDictionaryRef backup
, CFErrorRef
*error
)
2989 SOSManifestRef mold
= NULL
, mnow
= NULL
, mdelete
= NULL
, madd
= NULL
;
2990 __block CFMutableDictionaryRef backup_new
= NULL
;
2991 keybag_handle_t bag_handle
;
2992 if (!ks_open_keybag(keybag
, password
, &bag_handle
, error
))
2995 // We need to have a datasource singleton for protection domain
2996 // kSecAttrAccessibleWhenUnlocked and keep a single shared engine
2997 // instance around which we create in the datasource constructor as well.
2998 SOSDataSourceFactoryRef dsf
= SecItemDataSourceFactoryGetDefault();
2999 SOSDataSourceRef ds
= SOSDataSourceFactoryCreateDataSource(dsf
, kSecAttrAccessibleWhenUnlocked
, error
);
3001 backup_new
= backup
? CFDictionaryCreateMutableCopy(kCFAllocatorDefault
, 0, backup
) : CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
3002 mold
= SOSCreateManifestWithBackup(backup
, error
);
3003 SOSEngineRef engine
= SOSDataSourceGetSharedEngine(ds
, error
);
3004 mnow
= SOSEngineCopyManifest(engine
, NULL
);
3006 mnow
= SOSDataSourceCopyManifestWithViewNameSet(ds
, SOSViewsGetV0ViewSet(), error
);
3009 CFReleaseNull(backup_new
);
3010 secerror("failed to obtain manifest for keychain: %@", error
? *error
: NULL
);
3012 SOSManifestDiff(mold
, mnow
, &mdelete
, &madd
, error
);
3015 // Delete everything from the new_backup that is no longer in the datasource according to the datasources manifest.
3016 SOSManifestForEach(mdelete
, ^(CFDataRef digest_data
, bool *stop
) {
3017 CFStringRef deleted_item_key
= CFDataCopyHexString(digest_data
);
3018 CFDictionaryRemoveValue(backup_new
, deleted_item_key
);
3019 CFRelease(deleted_item_key
);
3022 CFMutableArrayRef changes
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
3023 SOSDataSourceForEachObject(ds
, NULL
, madd
, error
, ^void(CFDataRef digest
, SOSObjectRef object
, bool *stop
) {
3024 CFErrorRef localError
= NULL
;
3025 CFDataRef digest_data
= NULL
;
3026 CFTypeRef value
= NULL
;
3028 // Key in our manifest can't be found in db, remove it from our manifest
3029 SOSChangesAppendDelete(changes
, digest
);
3030 } else if (!(digest_data
= SOSObjectCopyDigest(ds
, object
, &localError
))
3031 || !(value
= SOSObjectCopyBackup(ds
, object
, bag_handle
, &localError
))) {
3032 if (SecErrorGetOSStatus(localError
) == errSecDecode
) {
3033 // Ignore decode errors, pretend the objects aren't there
3034 CFRelease(localError
);
3035 // Object undecodable, remove it from our manifest
3036 SOSChangesAppendDelete(changes
, digest
);
3038 // Stop iterating and propagate out all other errors.
3040 *error
= localError
;
3041 CFReleaseNull(backup_new
);
3044 // TODO: Should we skip tombstones here?
3045 CFStringRef key
= CFDataCopyHexString(digest_data
);
3046 CFDictionarySetValue(backup_new
, key
, value
);
3049 CFReleaseSafe(digest_data
);
3050 CFReleaseSafe(value
);
3051 }) || CFReleaseNull(backup_new
);
3053 if (CFArrayGetCount(changes
)) {
3054 if (!SOSEngineUpdateChanges(engine
, kSOSDataSourceSOSTransaction
, changes
, error
)) {
3055 CFReleaseNull(backup_new
);
3058 CFReleaseSafe(changes
);
3060 SOSDataSourceRelease(ds
, error
) || CFReleaseNull(backup_new
);
3063 CFReleaseSafe(mold
);
3064 CFReleaseSafe(mnow
);
3065 CFReleaseSafe(madd
);
3066 CFReleaseSafe(mdelete
);
3067 ks_close_keybag(bag_handle
, error
) || CFReleaseNull(backup_new
);
3073 _SecServerRestoreTruthInTheCloud(CFDataRef keybag
, CFDataRef password
, CFDictionaryRef backup_in
, CFErrorRef
*error
) {
3074 __block
bool ok
= true;
3075 keybag_handle_t bag_handle
;
3076 if (!ks_open_keybag(keybag
, password
, &bag_handle
, error
))
3079 SOSManifestRef mbackup
= SOSCreateManifestWithBackup(backup_in
, error
);
3081 SOSDataSourceFactoryRef dsf
= SecItemDataSourceFactoryGetDefault();
3082 SOSDataSourceRef ds
= SOSDataSourceFactoryCreateDataSource(dsf
, kSecAttrAccessibleWhenUnlocked
, error
);
3083 ok
&= ds
&& SOSDataSourceWith(ds
, error
, ^(SOSTransactionRef txn
, bool *commit
) {
3084 SOSManifestRef mnow
= SOSDataSourceCopyManifestWithViewNameSet(ds
, SOSViewsGetV0BackupViewSet(), error
);
3085 SOSManifestRef mdelete
= NULL
, madd
= NULL
;
3086 SOSManifestDiff(mnow
, mbackup
, &mdelete
, &madd
, error
);
3088 // Don't delete everything in datasource not in backup.
3090 // Add items from the backup
3091 SOSManifestForEach(madd
, ^void(CFDataRef e
, bool *stop
) {
3092 CFDictionaryRef item
= NULL
;
3093 CFStringRef sha1
= CFDataCopyHexString(e
);
3095 item
= CFDictionaryGetValue(backup_in
, sha1
);
3099 CFErrorRef localError
= NULL
;
3101 if (!SOSObjectRestoreObject(ds
, txn
, bag_handle
, item
, &localError
)) {
3102 OSStatus status
= SecErrorGetOSStatus(localError
);
3103 if (status
== errSecDuplicateItem
) {
3104 // Log and ignore duplicate item errors during restore
3105 secnotice("titc", "restore %@ not replacing existing item", item
);
3106 } else if (status
== errSecDecode
) {
3107 // Log and ignore corrupted item errors during restore
3108 secnotice("titc", "restore %@ skipping corrupted item %@", item
, localError
);
3110 if (status
== errSecInteractionNotAllowed
)
3112 // Propagate the first other error upwards (causing the restore to fail).
3113 secerror("restore %@ failed %@", item
, localError
);
3115 if (error
&& !*error
) {
3116 *error
= localError
;
3120 CFReleaseSafe(localError
);
3124 ok
&= SOSDataSourceRelease(ds
, error
);
3125 CFReleaseNull(mdelete
);
3126 CFReleaseNull(madd
);
3127 CFReleaseNull(mnow
);
3132 ok
&= ks_close_keybag(bag_handle
, error
);
3138 CF_RETURNS_RETAINED CFDictionaryRef
3139 _SecServerBackupSyncable(CFDictionaryRef backup
, CFDataRef keybag
, CFDataRef password
, CFErrorRef
*error
) {
3140 require_action_quiet(isData(keybag
), errOut
, SecError(errSecParam
, error
, CFSTR("keybag %@ not a data"), keybag
));
3141 require_action_quiet(!backup
|| isDictionary(backup
), errOut
, SecError(errSecParam
, error
, CFSTR("backup %@ not a dictionary"), backup
));
3142 require_action_quiet(!password
|| isData(password
), errOut
, SecError(errSecParam
, error
, CFSTR("password %@ not a data"), password
));
3144 return _SecServerCopyTruthInTheCloud(keybag
, password
, backup
, error
);
3151 _SecServerRestoreSyncable(CFDictionaryRef backup
, CFDataRef keybag
, CFDataRef password
, CFErrorRef
*error
) {
3153 require_action_quiet(isData(keybag
), errOut
, ok
= SecError(errSecParam
, error
, CFSTR("keybag %@ not a data"), keybag
));
3154 require_action_quiet(isDictionary(backup
), errOut
, ok
= SecError(errSecParam
, error
, CFSTR("backup %@ not a dictionary"), backup
));
3157 require_action_quiet(isData(password
), errOut
, ok
= SecError(errSecParam
, error
, CFSTR("password not a data")));
3160 ok
= _SecServerRestoreTruthInTheCloud(keybag
, password
, backup
, error
);
3166 bool _SecServerRollKeysGlue(bool force
, CFErrorRef
*error
) {
3167 return _SecServerRollKeys(force
, NULL
, error
);
3171 bool _SecServerRollKeys(bool force
, SecurityClient
*client
, CFErrorRef
*error
) {
3173 uint32_t keystore_generation_status
= 0;
3174 if (aks_generation(KEYBAG_DEVICE
, generation_noop
, &keystore_generation_status
))
3176 uint32_t current_generation
= keystore_generation_status
& generation_current
;
3178 return kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
3179 bool up_to_date
= s3dl_dbt_keys_current(dbt
, current_generation
, NULL
);
3181 if (force
&& !up_to_date
) {
3182 up_to_date
= s3dl_dbt_update_keys(dbt
, client
, error
);
3184 secerror("Completed roll keys.");
3185 up_to_date
= s3dl_dbt_keys_current(dbt
, current_generation
, NULL
);
3188 secerror("Failed to roll keys.");
3198 InitialSyncItems(CFMutableArrayRef items
, bool limitToCurrent
, CFStringRef agrp
, CFStringRef svce
, const SecDbClass
*qclass
, CFErrorRef
*error
)
3200 bool result
= false;
3203 q
= query_create(qclass
, NULL
, NULL
, error
);
3206 q
->q_return_type
= kSecReturnDataMask
| kSecReturnAttributesMask
;
3207 q
->q_limit
= kSecMatchUnlimited
;
3208 q
->q_keybag
= KEYBAG_DEVICE
;
3210 query_add_attribute(kSecAttrAccessGroup
, agrp
, q
);
3211 query_add_attribute(kSecAttrSynchronizable
, kCFBooleanTrue
, q
);
3212 query_add_attribute(kSecAttrTombstone
, kCFBooleanFalse
, q
);
3214 query_add_attribute(kSecAttrService
, svce
, q
);
3216 result
= kc_with_dbt(false, error
, ^(SecDbConnectionRef dbt
) {
3217 return kc_transaction(dbt
, error
, ^{
3218 CFErrorRef error2
= NULL
;
3220 SecDbItemSelect(q
, dbt
, &error2
, NULL
, ^bool(const SecDbAttr
*attr
) {
3221 return CFDictionaryGetValue(q
->q_item
, attr
->name
);
3222 }, NULL
, NULL
, ^(SecDbItemRef item
, bool *stop
) {
3223 CFErrorRef error3
= NULL
;
3224 secinfo("InitialSyncItems", "Copy item");
3226 CFMutableDictionaryRef attrs
= SecDbItemCopyPListWithMask(item
, kSecDbSyncFlag
, &error3
);
3229 CFStringRef itemvwht
= CFDictionaryGetValue(attrs
, kSecAttrSyncViewHint
);
3231 * Saying its a SOS viewhint is really not the right answer post Triangle
3233 if (isString(itemvwht
) && !SOSViewInSOSSystem(itemvwht
)) {
3237 * Here we encode how PCS stores identities so that we only copy the
3238 * current identites for performance reasons.
3240 if (limitToCurrent
) {
3241 enum { PCS_CURRENT_IDENTITY_OFFSET
= 0x10000 };
3244 CFNumberRef type
= CFDictionaryGetValue(attrs
, kSecAttrType
);
3245 if (!isNumber(type
)) {
3246 // still allow this case since its not a service identity ??
3247 } else if (!CFNumberGetValue(type
, kCFNumberSInt32Type
, &s32
)) {
3249 } else if ((s32
& PCS_CURRENT_IDENTITY_OFFSET
) == 0) {
3254 CFDictionaryAddValue(attrs
, kSecClass
, SecDbItemGetClass(item
)->name
);
3255 CFArrayAppendValue(items
, attrs
);
3258 CFReleaseNull(attrs
);
3260 CFReleaseNull(error3
);
3262 CFReleaseNull(error2
);
3270 query_destroy(q
, NULL
);
3275 _SecServerCopyInitialSyncCredentials(uint32_t flags
, CFErrorRef
*error
)
3277 CFMutableArrayRef items
= CFArrayCreateMutableForCFTypes(NULL
);
3279 if (flags
& SecServerInitialSyncCredentialFlagTLK
) {
3280 require_action(InitialSyncItems(items
, false, CFSTR("com.apple.security.ckks"), NULL
, inet_class(), error
), fail
,
3281 secerror("failed to collect CKKS-inet keys: %@", error
? *error
: NULL
));
3283 if (flags
& SecServerInitialSyncCredentialFlagPCS
) {
3284 bool onlyCurrent
= !(flags
& SecServerInitialSyncCredentialFlagPCSNonCurrent
);
3286 require_action(InitialSyncItems(items
, false, CFSTR("com.apple.ProtectedCloudStorage"), NULL
, genp_class(), error
), fail
,
3287 secerror("failed to collect PCS-genp keys: %@", error
? *error
: NULL
));
3288 require_action(InitialSyncItems(items
, onlyCurrent
, CFSTR("com.apple.ProtectedCloudStorage"), NULL
, inet_class(), error
), fail
,
3289 secerror("failed to collect PCS-inet keys: %@", error
? *error
: NULL
));
3291 if (flags
& SecServerInitialSyncCredentialFlagBluetoothMigration
) {
3292 require_action(InitialSyncItems(items
, false, CFSTR("com.apple.nanoregistry.migration"), NULL
, genp_class(), error
), fail
,
3293 secerror("failed to collect com.apple.nanoregistry.migration-genp item: %@", error
? *error
: NULL
));
3294 require_action(InitialSyncItems(items
, false, CFSTR("com.apple.nanoregistry.migration2"), NULL
, genp_class(), error
), fail
,
3295 secerror("failed to collect com.apple.nanoregistry.migration2-genp item: %@", error
? *error
: NULL
));
3296 require_action(InitialSyncItems(items
, false, CFSTR("com.apple.bluetooth"), CFSTR("BluetoothLESync"), genp_class(), error
), fail
,
3297 secerror("failed to collect com.apple.bluetooth-genp item: %@", error
? *error
: NULL
));
3306 _SecServerImportInitialSyncCredentials(CFArrayRef array
, CFErrorRef
*error
)
3308 return kc_with_dbt(true, error
, ^bool(SecDbConnectionRef dbt
) {
3309 return kc_transaction(dbt
, error
, ^bool(void){
3310 CFIndex n
, count
= CFArrayGetCount(array
);
3312 secinfo("ImportInitialSyncItems", "Importing %d items", (int)count
);
3314 for (n
= 0; n
< count
; n
++) {
3315 CFErrorRef cferror
= NULL
;
3317 CFDictionaryRef item
= CFArrayGetValueAtIndex(array
, n
);
3318 if (!isDictionary(item
))
3321 CFStringRef className
= CFDictionaryGetValue(item
, kSecClass
);
3322 if (className
== NULL
) {
3323 secinfo("ImportInitialSyncItems", "Item w/o class");
3327 const SecDbClass
*cls
= kc_class_with_name(className
);
3329 secinfo("ImportInitialSyncItems", "Item with unknown class: %@", className
);
3333 SecDbItemRef dbi
= SecDbItemCreateWithAttributes(NULL
, cls
, item
, KEYBAG_DEVICE
, &cferror
);
3335 secinfo("ImportInitialSyncItems", "Item creation failed with: %@", cferror
);
3336 CFReleaseNull(cferror
);
3340 if (!SecDbItemSetSyncable(dbi
, true, &cferror
)) {
3341 secinfo("ImportInitialSyncItems", "Failed to set sync=1: %@ for item %@", cferror
, dbi
);
3342 CFReleaseNull(cferror
);
3347 if (!SecDbItemInsert(dbi
, dbt
, &cferror
)) {
3348 secinfo("ImportInitialSyncItems", "Item store failed with: %@: %@", cferror
, dbi
);
3349 CFReleaseNull(cferror
);
3363 * Sync bubble migration code
3366 struct SyncBubbleRule
{
3367 CFStringRef attribute
;
3372 TransmogrifyItemsToSyncBubble(SecurityClient
*client
, uid_t uid
,
3375 const SecDbClass
*qclass
,
3376 struct SyncBubbleRule
*items
, CFIndex nItems
,
3379 CFMutableDictionaryRef updateAttributes
= NULL
;
3380 CFDataRef syncBubbleView
= NULL
;
3381 CFDataRef activeUserView
= NULL
;
3386 syncBubbleView
= SecMUSRCreateSyncBubbleUserUUID(uid
);
3387 require(syncBubbleView
, fail
);
3389 activeUserView
= SecMUSRCreateActiveUserUUID(uid
);
3390 require(activeUserView
, fail
);
3393 if ((onlyDelete
&& !copyToo
) || !onlyDelete
) {
3396 * Clean out items first
3399 secnotice("syncbubble", "cleaning out old items");
3401 q
= query_create(qclass
, NULL
, NULL
, error
);
3404 q
->q_limit
= kSecMatchUnlimited
;
3405 q
->q_keybag
= device_keybag_handle
;
3407 for (n
= 0; n
< nItems
; n
++) {
3408 query_add_attribute(items
[n
].attribute
, items
[n
].value
, q
);
3410 q
->q_musrView
= CFRetain(syncBubbleView
);
3411 require(q
->q_musrView
, fail
);
3413 kc_with_dbt(false, error
, ^(SecDbConnectionRef dbt
) {
3414 return kc_transaction(dbt
, error
, ^{
3415 return s3dl_query_delete(dbt
, q
, NULL
, error
);
3419 query_destroy(q
, NULL
);
3424 if (onlyDelete
|| !copyToo
) {
3425 secnotice("syncbubble", "skip migration of items");
3428 * Copy over items from EMCS to sync bubble
3431 secnotice("syncbubble", "migrating sync bubble items");
3433 q
= query_create(qclass
, NULL
, NULL
, error
);
3436 q
->q_return_type
= kSecReturnDataMask
| kSecReturnAttributesMask
;
3437 q
->q_limit
= kSecMatchUnlimited
;
3438 q
->q_keybag
= device_keybag_handle
; /* XXX change to session key bag when it exists */
3440 for (n
= 0; n
< nItems
; n
++) {
3441 query_add_or_attribute(items
[n
].attribute
, items
[n
].value
, q
);
3443 query_add_or_attribute(CFSTR("musr"), activeUserView
, q
);
3444 q
->q_musrView
= CFRetain(activeUserView
);
3446 updateAttributes
= CFDictionaryCreateMutableForCFTypes(NULL
);
3447 require(updateAttributes
, fail
);
3449 CFDictionarySetValue(updateAttributes
, CFSTR("musr"), syncBubbleView
); /* XXX should use kSecAttrMultiUser */
3452 kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
3453 return kc_transaction(dbt
, error
, ^{
3454 CFErrorRef error2
= NULL
;
3456 SecDbItemSelect(q
, dbt
, &error2
, NULL
, ^bool(const SecDbAttr
*attr
) {
3457 return CFDictionaryGetValue(q
->q_item
, attr
->name
);
3458 }, NULL
, NULL
, ^(SecDbItemRef item
, bool *stop
) {
3459 CFErrorRef error3
= NULL
;
3460 secinfo("syncbubble", "migrating item");
3462 SecDbItemRef new_item
= SecDbItemCopyWithUpdates(item
, updateAttributes
, NULL
);
3463 if (new_item
== NULL
)
3466 SecDbItemClearRowId(new_item
, NULL
);
3468 if (!SecDbItemSetKeybag(new_item
, device_keybag_handle
, NULL
)) {
3469 CFRelease(new_item
);
3473 if (!SecDbItemInsert(new_item
, dbt
, &error3
)) {
3474 secnotice("syncbubble", "migration failed with %@ for item %@", error3
, new_item
);
3476 CFRelease(new_item
);
3477 CFReleaseNull(error3
);
3479 CFReleaseNull(error2
);
3488 CFReleaseNull(syncBubbleView
);
3489 CFReleaseNull(activeUserView
);
3490 CFReleaseNull(updateAttributes
);
3492 query_destroy(q
, NULL
);
3497 static struct SyncBubbleRule PCSItems
[] = {
3499 .attribute
= CFSTR("agrp"),
3500 .value
= CFSTR("com.apple.ProtectedCloudStorage"),
3503 static struct SyncBubbleRule NSURLSesssiond
[] = {
3505 .attribute
= CFSTR("agrp"),
3506 .value
= CFSTR("com.apple.nsurlsessiond"),
3509 static struct SyncBubbleRule AccountsdItems
[] = {
3511 .attribute
= CFSTR("svce"),
3512 .value
= CFSTR("com.apple.account.AppleAccount.token"),
3515 .attribute
= CFSTR("svce"),
3516 .value
= CFSTR("com.apple.account.AppleAccount.password"),
3519 .attribute
= CFSTR("svce"),
3520 .value
= CFSTR("com.apple.account.AppleAccount.rpassword"),
3523 .attribute
= CFSTR("svce"),
3524 .value
= CFSTR("com.apple.account.idms.token"),
3527 .attribute
= CFSTR("svce"),
3528 .value
= CFSTR("com.apple.account.idms.continuation-key"),
3531 .attribute
= CFSTR("svce"),
3532 .value
= CFSTR("com.apple.account.CloudKit.token"),
3536 static struct SyncBubbleRule MobileMailItems
[] = {
3538 .attribute
= CFSTR("svce"),
3539 .value
= CFSTR("com.apple.account.IMAP.password"),
3542 .attribute
= CFSTR("svce"),
3543 .value
= CFSTR("com.apple.account.SMTP.password"),
3546 .attribute
= CFSTR("svce"),
3547 .value
= CFSTR("com.apple.account.Exchange.password"),
3550 .attribute
= CFSTR("svce"),
3551 .value
= CFSTR("com.apple.account.Hotmail.password"),
3554 .attribute
= CFSTR("svce"),
3555 .value
= CFSTR("com.apple.account.Google.password"),
3558 .attribute
= CFSTR("svce"),
3559 .value
= CFSTR("com.apple.account.Google.oauth-token"),
3562 .attribute
= CFSTR("svce"),
3563 .value
= CFSTR("com.apple.account.Google.oath-refresh-token"),
3566 .attribute
= CFSTR("svce"),
3567 .value
= CFSTR("com.apple.account.Yahoo.password"),
3570 .attribute
= CFSTR("svce"),
3571 .value
= CFSTR("com.apple.account.Yahoo.oauth-token"),
3574 .attribute
= CFSTR("svce"),
3575 .value
= CFSTR("com.apple.account.Yahoo.oauth-token-nosync"),
3578 .attribute
= CFSTR("svce"),
3579 .value
= CFSTR("com.apple.account.Yahoo.oath-refresh-token"),
3582 .attribute
= CFSTR("svce"),
3583 .value
= CFSTR("com.apple.account.IMAPNotes.password"),
3586 .attribute
= CFSTR("svce"),
3587 .value
= CFSTR("com.apple.account.IMAPMail.password"),
3590 .attribute
= CFSTR("svce"),
3591 .value
= CFSTR("com.apple.account.126.password"),
3594 .attribute
= CFSTR("svce"),
3595 .value
= CFSTR("com.apple.account.163.password"),
3598 .attribute
= CFSTR("svce"),
3599 .value
= CFSTR("com.apple.account.aol.password"),
3604 ArrayContains(CFArrayRef array
, CFStringRef service
)
3606 return CFArrayContainsValue(array
, CFRangeMake(0, CFArrayGetCount(array
)), service
);
3610 _SecServerTransmogrifyToSyncBubble(CFArrayRef services
, uid_t uid
, SecurityClient
*client
, CFErrorRef
*error
)
3612 bool copyCloudAuthToken
= false;
3613 bool copyMobileMail
= false;
3615 bool copyPCS
= false;
3616 bool onlyDelete
= false;
3617 bool copyNSURLSesssion
= false;
3619 if (!client
->inMultiUser
)
3622 secnotice("syncbubble", "migration for uid %d uid for services %@", (int)uid
, services
);
3624 #if TARGET_OS_SIMULATOR
3627 if (uid
!= (uid_t
)client
->activeUser
)
3630 #error "no sync bubble on other platforms"
3634 * First select that services to copy/delete
3637 if (ArrayContains(services
, CFSTR("com.apple.bird.usermanager.sync"))
3638 || ArrayContains(services
, CFSTR("com.apple.cloudphotod.sync"))
3639 || ArrayContains(services
, CFSTR("com.apple.cloudphotod.syncstakeholder"))
3640 || ArrayContains(services
, CFSTR("com.apple.cloudd.usermanager.sync")))
3642 copyCloudAuthToken
= true;
3646 if (ArrayContains(services
, CFSTR("com.apple.nsurlsessiond.usermanager.sync")))
3648 copyCloudAuthToken
= true;
3649 copyNSURLSesssion
= true;
3652 if (ArrayContains(services
, CFSTR("com.apple.syncdefaultsd.usermanager.sync"))) {
3653 copyCloudAuthToken
= true;
3655 if (ArrayContains(services
, CFSTR("com.apple.mailq.sync")) || ArrayContains(services
, CFSTR("com.apple.mailq.sync.xpc"))) {
3656 copyCloudAuthToken
= true;
3657 copyMobileMail
= true;
3662 * The actually copy/delete the items selected
3665 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyPCS
, inet_class(), PCSItems
, sizeof(PCSItems
)/sizeof(PCSItems
[0]), error
);
3667 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyPCS
, genp_class(), PCSItems
, sizeof(PCSItems
)/sizeof(PCSItems
[0]), error
);
3671 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyMobileMail
, genp_class(), MobileMailItems
, sizeof(MobileMailItems
)/sizeof(MobileMailItems
[0]), error
);
3675 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyCloudAuthToken
, genp_class(), AccountsdItems
, sizeof(AccountsdItems
)/sizeof(AccountsdItems
[0]), error
);
3679 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyNSURLSesssion
, inet_class(), NSURLSesssiond
, sizeof(NSURLSesssiond
)/sizeof(NSURLSesssiond
[0]), error
);
3687 * Migrate from user keychain to system keychain when switching to edu mode
3691 _SecServerTransmogrifyToSystemKeychain(SecurityClient
*client
, CFErrorRef
*error
)
3693 __block
bool ok
= true;
3696 * we are not in multi user yet, about to switch, otherwise we would
3697 * check that for client->inMultiuser here
3700 kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
3701 return kc_transaction(dbt
, error
, ^{
3702 CFDataRef systemUUID
= SecMUSRGetSystemKeychainUUID();
3704 const SecDbSchema
*newSchema
= current_schema();
3705 SecDbClass
const *const *kcClass
;
3707 for (kcClass
= newSchema
->classes
; *kcClass
!= NULL
; kcClass
++) {
3708 CFErrorRef localError
= NULL
;
3711 if (!((*kcClass
)->itemclass
)) {
3715 q
= query_create(*kcClass
, SecMUSRGetSingleUserKeychainUUID(), NULL
, error
);
3719 ok
&= SecDbItemSelect(q
, dbt
, error
, ^bool(const SecDbAttr
*attr
) {
3720 return (attr
->flags
& kSecDbInFlag
) != 0;
3721 }, ^bool(const SecDbAttr
*attr
) {
3722 // No filtering please.
3724 }, ^bool(CFMutableStringRef sql
, bool *needWhere
) {
3725 SecDbAppendWhereOrAnd(sql
, needWhere
);
3726 CFStringAppendFormat(sql
, NULL
, CFSTR("musr = ?"));
3728 }, ^bool(sqlite3_stmt
*stmt
, int col
) {
3729 return SecDbBindObject(stmt
, col
++, SecMUSRGetSingleUserKeychainUUID(), error
);
3730 }, ^(SecDbItemRef item
, bool *stop
) {
3731 CFErrorRef localError
= NULL
;
3733 if (!SecDbItemSetValueWithName(item
, kSecAttrMultiUser
, systemUUID
, &localError
)) {
3734 secerror("item: %@ update musr to system failed: %@", item
, localError
);
3739 if (!SecDbItemDoUpdate(item
, item
, dbt
, &localError
, ^bool (const SecDbAttr
*attr
) {
3740 return attr
->kind
== kSecDbRowIdAttr
;
3742 secerror("item: %@ insert during UPDATE: %@", item
, localError
);
3748 SecErrorPropagate(localError
, error
);
3752 query_destroy(q
, &localError
);
3763 * Delete account from local usage
3767 _SecServerDeleteMUSERViews(SecurityClient
*client
, uid_t uid
, CFErrorRef
*error
)
3769 return kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
3770 CFDataRef musrView
= NULL
, syncBubbleView
= NULL
;
3773 syncBubbleView
= SecMUSRCreateSyncBubbleUserUUID(uid
);
3774 require(syncBubbleView
, fail
);
3776 musrView
= SecMUSRCreateActiveUserUUID(uid
);
3777 require(musrView
, fail
);
3779 require(ok
= SecServerDeleteAllForUser(dbt
, syncBubbleView
, false, error
), fail
);
3780 require(ok
= SecServerDeleteAllForUser(dbt
, musrView
, false, error
), fail
);
3783 CFReleaseNull(syncBubbleView
);
3784 CFReleaseNull(musrView
);
3790 #endif /* TARGET_OS_IOS */
3793 _SecServerGetKeyStats(const SecDbClass
*qclass
,
3794 struct _SecServerKeyStats
*stats
)
3796 __block CFErrorRef error
= NULL
;
3799 Query
*q
= query_create(qclass
, NULL
, NULL
, &error
);
3802 q
->q_return_type
= kSecReturnDataMask
| kSecReturnAttributesMask
;
3803 q
->q_limit
= kSecMatchUnlimited
;
3804 q
->q_keybag
= KEYBAG_DEVICE
;
3805 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleWhenUnlocked
, q
);
3806 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleAfterFirstUnlock
, q
);
3807 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleAlways
, q
);
3808 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleWhenUnlockedThisDeviceOnly
, q
);
3809 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
, q
);
3810 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleAlwaysThisDeviceOnly
, q
);
3811 query_add_attribute(kSecAttrTombstone
, kCFBooleanFalse
, q
);
3813 kc_with_dbt(false, &error
, ^(SecDbConnectionRef dbconn
) {
3814 CFErrorRef error2
= NULL
;
3815 __block CFIndex totalSize
= 0;
3816 stats
->maxDataSize
= 0;
3818 SecDbItemSelect(q
, dbconn
, &error2
, NULL
, ^bool(const SecDbAttr
*attr
) {
3819 return CFDictionaryContainsKey(q
->q_item
, attr
->name
);
3820 }, NULL
, NULL
, ^(SecDbItemRef item
, bool *stop
) {
3821 CFErrorRef error3
= NULL
;
3822 CFDataRef data
= SecDbItemGetValue(item
, &v6v_Data
, &error3
);
3824 CFIndex size
= CFDataGetLength(data
);
3825 if (size
> stats
->maxDataSize
)
3826 stats
->maxDataSize
= size
;
3830 CFReleaseNull(error3
);
3832 CFReleaseNull(error2
);
3834 stats
->averageSize
= totalSize
/ stats
->items
;
3843 CFReleaseNull(error
);
3845 query_destroy(q
, NULL
);
3849 CFArrayRef
_SecItemCopyParentCertificates(CFDataRef normalizedIssuer
, CFArrayRef accessGroups
, CFErrorRef
*error
) {
3850 const void *keys
[] = {
3857 kSecClassCertificate
,
3862 CFDictionaryRef query
= CFDictionaryCreate(NULL
, keys
, values
, 4,
3864 CFTypeRef results
= NULL
;
3865 SecurityClient client
= {
3867 .accessGroups
= accessGroups
,
3868 .allowSystemKeychain
= true,
3869 .allowSyncBubbleKeychain
= false,
3870 .isNetworkExtension
= false,
3873 (void)_SecItemCopyMatching(query
, &client
, &results
, error
);
3878 bool _SecItemCertificateExists(CFDataRef normalizedIssuer
, CFDataRef serialNumber
, CFArrayRef accessGroups
, CFErrorRef
*error
) {
3879 const void *keys
[] = {
3883 kSecAttrSerialNumber
3886 kSecClassCertificate
,
3891 SecurityClient client
= {
3893 .accessGroups
= accessGroups
,
3894 .allowSystemKeychain
= true,
3895 .allowSyncBubbleKeychain
= false,
3896 .isNetworkExtension
= false,
3898 CFDictionaryRef query
= CFDictionaryCreate(NULL
, keys
, values
, 4, NULL
, NULL
);
3899 CFTypeRef results
= NULL
;
3900 bool ok
= _SecItemCopyMatching(query
, &client
, &results
, error
);
3901 CFReleaseSafe(query
);
3902 CFReleaseSafe(results
);