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 /* AUDIT[securityd](done):
2042 No caller provided inputs.
2045 SecItemServerDeleteAll(CFErrorRef
*error
) {
2046 secerror("SecItemServerDeleteAll");
2047 return kc_with_dbt(true, error
, ^bool (SecDbConnectionRef dbt
) {
2048 return (kc_transaction(dbt
, error
, ^bool {
2049 return (SecDbExec(dbt
, CFSTR("DELETE from genp;"), error
) &&
2050 SecDbExec(dbt
, CFSTR("DELETE from inet;"), error
) &&
2051 SecDbExec(dbt
, CFSTR("DELETE from cert;"), error
) &&
2052 SecDbExec(dbt
, CFSTR("DELETE from keys;"), error
));
2053 }) && SecDbExec(dbt
, CFSTR("VACUUM;"), error
));
2058 _SecItemDeleteAll(CFErrorRef
*error
) {
2059 return SecItemServerDeleteAll(error
);
2063 _SecItemServerDeleteAllWithAccessGroups(CFArrayRef accessGroups
, SecurityClient
*client
, CFErrorRef
*error
)
2065 __block
bool ok
= true;
2066 static dispatch_once_t onceToken
;
2067 static CFSetRef illegalAccessGroups
= NULL
;
2069 dispatch_once(&onceToken
, ^{
2070 const CFStringRef values
[] = {
2073 CFSTR("com.apple.security.sos"),
2074 CFSTR("lockdown-identities"),
2076 illegalAccessGroups
= CFSetCreate(NULL
, (const void **)values
, sizeof(values
)/sizeof(values
[0]), &kCFTypeSetCallBacks
);
2079 static CFTypeRef qclasses
[] = {
2085 // strange construction needed for schema indirection
2086 static dispatch_once_t qclassesOnceToken
;
2087 dispatch_once(&qclassesOnceToken
, ^{
2088 qclasses
[0] = inet_class();
2089 qclasses
[1] = genp_class();
2090 qclasses
[2] = keys_class();
2091 qclasses
[3] = cert_class();
2094 require_action_quiet(isArray(accessGroups
), fail
,
2096 SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType
, sSecXPCErrorDomain
, NULL
, error
, NULL
, CFSTR("accessGroups not CFArray, got %@"), accessGroups
));
2098 // TODO: whitelist instead? look for dev IDs like 7123498YQX.com.somedev.app
2100 require_action(CFArrayGetCount(accessGroups
) != 0, fail
,
2102 SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType
, sSecXPCErrorDomain
, NULL
, error
, NULL
, CFSTR("accessGroups e empty")));
2105 // Pre-check accessGroups for prohibited values
2106 CFArrayForEach(accessGroups
, ^(const void *value
) {
2107 CFStringRef agrp
= (CFStringRef
)value
;
2109 if (!isString(agrp
)) {
2110 SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType
, sSecXPCErrorDomain
, NULL
, error
, NULL
,
2111 CFSTR("access not a string: %@"), agrp
);
2113 } else if (CFSetContainsValue(illegalAccessGroups
, agrp
)) {
2114 SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType
, sSecXPCErrorDomain
, NULL
, error
, NULL
,
2115 CFSTR("illegal access group: %@"), accessGroups
);
2121 ok
= kc_with_dbt(true, error
, ^bool(SecDbConnectionRef dbt
) {
2122 return kc_transaction(dbt
, error
, ^bool {
2123 CFErrorRef localError
= NULL
;
2127 for (n
= 0; n
< sizeof(qclasses
)/sizeof(qclasses
[0]) && ok1
; n
++) {
2130 q
= query_create(qclasses
[n
], client
->musr
, NULL
, error
);
2133 (void)s3dl_query_delete(dbt
, q
, accessGroups
, &localError
);
2135 query_destroy(q
, error
);
2136 CFReleaseNull(localError
);
2139 }) && SecDbExec(dbt
, CFSTR("VACUUM"), error
);
2148 // MARK: Shared web credentials
2150 #if TARGET_OS_IOS && !TARGET_OS_BRIDGE
2153 #define SEC_CONST_DECL(k,v) const CFStringRef k = CFSTR(v);
2155 SEC_CONST_DECL (kSecSafariAccessGroup
, "com.apple.cfnetwork");
2156 SEC_CONST_DECL (kSecSafariDefaultComment
, "default");
2157 SEC_CONST_DECL (kSecSafariPasswordsNotSaved
, "Passwords not saved");
2158 SEC_CONST_DECL (kSecSharedCredentialUrlScheme
, "https://");
2159 SEC_CONST_DECL (kSecSharedWebCredentialsService
, "webcredentials");
2161 #if !TARGET_IPHONE_SIMULATOR
2163 _SecAppDomainApprovalStatus(CFStringRef appID
, CFStringRef fqdn
, CFErrorRef
*error
)
2165 __block SWCFlags flags
= kSWCFlags_None
;
2168 dispatch_semaphore_t semaphore
= dispatch_semaphore_create(0);
2169 if (semaphore
== NULL
)
2172 status
= SWCCheckService(kSecSharedWebCredentialsService
, appID
, fqdn
, ^void (OSStatus inStatus
, SWCFlags inFlags
, CFDictionaryRef inDetails
)
2174 if (inStatus
== 0) {
2177 secerror("SWCCheckService failed with %d", (int)inStatus
);
2179 dispatch_semaphore_signal(semaphore
);
2183 dispatch_semaphore_wait(semaphore
, DISPATCH_TIME_FOREVER
);
2185 secerror("SWCCheckService: failed to queue");
2187 dispatch_release(semaphore
);
2190 if (!(flags
& kSWCFlag_SiteApproved
)) {
2191 if (flags
& kSWCFlag_Pending
) {
2192 SecError(errSecAuthFailed
, error
, CFSTR("Approval is pending for \"%@\", try later"), fqdn
);
2194 SecError(errSecAuthFailed
, error
, CFSTR("\"%@\" failed to approve \"%@\""), fqdn
, appID
);
2196 } else if (flags
& kSWCFlag_UserDenied
) {
2197 SecError(errSecAuthFailed
, error
, CFSTR("User denied access to \"%@\" by \"%@\""), fqdn
, appID
);
2204 _SecEntitlementContainsDomainForService(CFArrayRef domains
, CFStringRef domain
, CFStringRef service
)
2206 bool result
= false;
2207 CFIndex idx
, count
= (domains
) ? CFArrayGetCount(domains
) : (CFIndex
) 0;
2208 if (!count
|| !domain
|| !service
) {
2211 for (idx
=0; idx
< count
; idx
++) {
2212 CFStringRef str
= (CFStringRef
) CFArrayGetValueAtIndex(domains
, idx
);
2213 if (str
&& CFStringHasPrefix(str
, kSecSharedWebCredentialsService
)) {
2214 CFIndex prefix_len
= CFStringGetLength(kSecSharedWebCredentialsService
)+1;
2215 CFIndex substr_len
= CFStringGetLength(str
) - prefix_len
;
2216 CFRange range
= { prefix_len
, substr_len
};
2217 CFStringRef substr
= CFStringCreateWithSubstring(kCFAllocatorDefault
, str
, range
);
2218 if (substr
&& CFEqual(substr
, domain
)) {
2221 CFReleaseSafe(substr
);
2229 #endif /* !TARGET_OS_SIMULATOR */
2232 _SecAddNegativeWebCredential(SecurityClient
*client
, CFStringRef fqdn
, CFStringRef appID
, bool forSafari
)
2234 #if !TARGET_IPHONE_SIMULATOR
2235 bool result
= false;
2236 if (!fqdn
) { return result
; }
2238 // update our database
2239 CFRetainSafe(appID
);
2241 if (0 == SWCSetServiceFlags(kSecSharedWebCredentialsService
, appID
, fqdn
, kSWCFlag_ExternalMask
, kSWCFlag_UserDenied
,
2242 ^void(OSStatus inStatus
, SWCFlags inNewFlags
){
2243 CFReleaseSafe(appID
);
2244 CFReleaseSafe(fqdn
);
2249 else // didn't queue the block
2251 CFReleaseSafe(appID
);
2252 CFReleaseSafe(fqdn
);
2255 if (!forSafari
) { return result
; }
2257 // below this point: create a negative Safari web credential item
2259 CFMutableDictionaryRef attrs
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2260 if (!attrs
) { return result
; }
2262 CFErrorRef error
= NULL
;
2263 CFStringRef accessGroup
= CFSTR("*");
2264 SecurityClient swcclient
= {
2266 .accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&accessGroup
, 1, &kCFTypeArrayCallBacks
),
2267 .allowSystemKeychain
= false,
2268 .allowSyncBubbleKeychain
= false,
2269 .isNetworkExtension
= false,
2270 .musr
= client
->musr
,
2273 CFDictionaryAddValue(attrs
, kSecClass
, kSecClassInternetPassword
);
2274 CFDictionaryAddValue(attrs
, kSecAttrAccessGroup
, kSecSafariAccessGroup
);
2275 CFDictionaryAddValue(attrs
, kSecAttrAuthenticationType
, kSecAttrAuthenticationTypeHTMLForm
);
2276 CFDictionaryAddValue(attrs
, kSecAttrProtocol
, kSecAttrProtocolHTTPS
);
2277 CFDictionaryAddValue(attrs
, kSecAttrServer
, fqdn
);
2278 CFDictionaryAddValue(attrs
, kSecAttrSynchronizable
, kCFBooleanTrue
);
2280 (void)_SecItemDelete(attrs
, &swcclient
, &error
);
2281 CFReleaseNull(error
);
2283 CFDictionaryAddValue(attrs
, kSecAttrAccount
, kSecSafariPasswordsNotSaved
);
2284 CFDictionaryAddValue(attrs
, kSecAttrComment
, kSecSafariDefaultComment
);
2286 CFStringRef label
= CFStringCreateWithFormat(kCFAllocatorDefault
,
2287 NULL
, CFSTR("%@ (%@)"), fqdn
, kSecSafariPasswordsNotSaved
);
2289 CFDictionaryAddValue(attrs
, kSecAttrLabel
, label
);
2290 CFReleaseSafe(label
);
2294 CFDataRef data
= CFDataCreate(kCFAllocatorDefault
, &space
, 1);
2296 CFDictionarySetValue(attrs
, kSecValueData
, data
);
2297 CFReleaseSafe(data
);
2300 CFTypeRef addResult
= NULL
;
2301 result
= _SecItemAdd(attrs
, &swcclient
, &addResult
, &error
);
2303 CFReleaseSafe(addResult
);
2304 CFReleaseSafe(error
);
2305 CFReleaseSafe(attrs
);
2306 CFReleaseSafe(swcclient
.accessGroups
);
2314 /* Specialized version of SecItemAdd for shared web credentials */
2316 _SecAddSharedWebCredential(CFDictionaryRef attributes
,
2317 SecurityClient
*client
,
2318 const audit_token_t
*clientAuditToken
,
2325 SecurityClient swcclient
= {};
2327 CFStringRef fqdn
= CFRetainSafe(CFDictionaryGetValue(attributes
, kSecAttrServer
));
2328 CFStringRef account
= CFDictionaryGetValue(attributes
, kSecAttrAccount
);
2329 #if TARGET_OS_IOS && !TARGET_OS_BRIDGE
2330 CFStringRef password
= CFDictionaryGetValue(attributes
, kSecSharedPassword
);
2332 CFStringRef password
= CFDictionaryGetValue(attributes
, CFSTR("spwd"));
2334 CFStringRef accessGroup
= CFSTR("*");
2335 CFMutableDictionaryRef query
= NULL
, attrs
= NULL
;
2339 // check autofill enabled status
2340 if (!swca_autofill_enabled(clientAuditToken
)) {
2341 SecError(errSecBadReq
, error
, CFSTR("Autofill is not enabled in Safari settings"));
2345 // parse fqdn with CFURL here, since it could be specified as domain:port
2347 CFStringRef urlStr
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%@%@"), kSecSharedCredentialUrlScheme
, fqdn
);
2349 CFURLRef url
= CFURLCreateWithString(kCFAllocatorDefault
, urlStr
, nil
);
2351 CFStringRef hostname
= CFURLCopyHostName(url
);
2353 CFReleaseSafe(fqdn
);
2355 port
= CFURLGetPortNumber(url
);
2359 CFReleaseSafe(urlStr
);
2364 SecError(errSecParam
, error
, CFSTR("No account provided"));
2368 SecError(errSecParam
, error
, CFSTR("No domain provided"));
2372 #if TARGET_IPHONE_SIMULATOR
2373 secerror("app/site association entitlements not checked in Simulator");
2375 OSStatus status
= errSecMissingEntitlement
;
2376 // validate that fqdn is part of caller's shared credential domains entitlement
2378 SecError(status
, error
, CFSTR("Missing application-identifier entitlement"));
2381 if (_SecEntitlementContainsDomainForService(domains
, fqdn
, kSecSharedWebCredentialsService
)) {
2382 status
= errSecSuccess
;
2384 if (errSecSuccess
!= status
) {
2385 CFStringRef msg
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
,
2386 CFSTR("%@ not found in %@ entitlement"), fqdn
, kSecEntitlementAssociatedDomains
);
2388 msg
= CFRetain(CFSTR("Requested domain not found in entitlement"));
2390 SecError(status
, error
, CFSTR("%@"), msg
);
2396 #if TARGET_IPHONE_SIMULATOR
2397 secerror("Ignoring app/site approval state in the Simulator.");
2399 // get approval status for this app/domain pair
2400 SWCFlags flags
= _SecAppDomainApprovalStatus(appID
, fqdn
, error
);
2401 if (!(flags
& kSWCFlag_SiteApproved
)) {
2406 // give ourselves access to see matching items for kSecSafariAccessGroup
2407 swcclient
.task
= NULL
;
2408 swcclient
.accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&accessGroup
, 1, &kCFTypeArrayCallBacks
);
2409 swcclient
.allowSystemKeychain
= false;
2410 swcclient
.musr
= client
->musr
;
2411 swcclient
.allowSystemKeychain
= false;
2412 swcclient
.allowSyncBubbleKeychain
= false;
2413 swcclient
.isNetworkExtension
= false;
2416 // create lookup query
2417 query
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2419 SecError(errSecAllocate
, error
, CFSTR("Unable to create query dictionary"));
2422 CFDictionaryAddValue(query
, kSecClass
, kSecClassInternetPassword
);
2423 CFDictionaryAddValue(query
, kSecAttrAccessGroup
, kSecSafariAccessGroup
);
2424 CFDictionaryAddValue(query
, kSecAttrAuthenticationType
, kSecAttrAuthenticationTypeHTMLForm
);
2425 CFDictionaryAddValue(query
, kSecAttrServer
, fqdn
);
2426 CFDictionaryAddValue(query
, kSecAttrSynchronizable
, kCFBooleanTrue
);
2428 // check for presence of Safari's negative entry ('passwords not saved')
2429 CFDictionarySetValue(query
, kSecAttrAccount
, kSecSafariPasswordsNotSaved
);
2430 ok
= _SecItemCopyMatching(query
, &swcclient
, result
, error
);
2431 if(result
) CFReleaseNull(*result
);
2432 if (error
) CFReleaseNull(*error
);
2434 SecError(errSecDuplicateItem
, error
, CFSTR("Item already exists for this server"));
2438 // now use the provided account (and optional port number, if one was present)
2439 CFDictionarySetValue(query
, kSecAttrAccount
, account
);
2440 if (port
< -1 || port
> 0) {
2441 SInt16 portValueShort
= (port
& 0xFFFF);
2442 CFNumberRef portNumber
= CFNumberCreate(NULL
, kCFNumberSInt16Type
, &portValueShort
);
2443 CFDictionaryAddValue(query
, kSecAttrPort
, portNumber
);
2444 CFReleaseSafe(portNumber
);
2447 // look up existing password
2448 CFDictionaryAddValue(query
, kSecReturnData
, kCFBooleanTrue
);
2449 bool matched
= _SecItemCopyMatching(query
, &swcclient
, result
, error
);
2450 CFDictionaryRemoveValue(query
, kSecReturnData
);
2452 // found it, so this becomes either an "update password" or "delete password" operation
2453 bool update
= (password
!= NULL
);
2455 attrs
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2456 CFDataRef credential
= CFStringCreateExternalRepresentation(kCFAllocatorDefault
, password
, kCFStringEncodingUTF8
, 0);
2457 CFDictionaryAddValue(attrs
, kSecValueData
, credential
);
2458 bool samePassword
= result
&& *result
&& CFEqual(*result
, credential
);
2459 CFReleaseSafe(credential
);
2460 CFDictionaryAddValue(attrs
, kSecAttrComment
, kSecSafariDefaultComment
);
2462 ok
= samePassword
|| swca_confirm_operation(swca_update_request_id
, clientAuditToken
, query
, error
,
2463 ^void (CFStringRef fqdn
) { _SecAddNegativeWebCredential(client
, fqdn
, appID
, false); });
2465 ok
= _SecItemUpdate(query
, attrs
, &swcclient
, error
);
2469 // confirm the delete
2470 // (per rdar://16676288 we always prompt, even if there was prior user approval)
2471 ok
= /*approved ||*/ swca_confirm_operation(swca_delete_request_id
, clientAuditToken
, query
, error
,
2472 ^void (CFStringRef fqdn
) { _SecAddNegativeWebCredential(client
, fqdn
, appID
, false); });
2474 ok
= _SecItemDelete(query
, &swcclient
, error
);
2478 if(result
) CFReleaseNull(*result
);
2479 if(error
) CFReleaseNull(*error
);
2483 if (result
) CFReleaseNull(*result
);
2484 if (error
) CFReleaseNull(*error
);
2486 // password does not exist, so prepare to add it
2488 // a NULL password value removes the existing credential. Since we didn't find it, this is a no-op.
2493 CFStringRef label
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%@ (%@)"), fqdn
, account
);
2495 CFDictionaryAddValue(query
, kSecAttrLabel
, label
);
2496 CFReleaseSafe(label
);
2498 // NOTE: we always expect to use HTTPS for web forms.
2499 CFDictionaryAddValue(query
, kSecAttrProtocol
, kSecAttrProtocolHTTPS
);
2501 CFDataRef credential
= CFStringCreateExternalRepresentation(kCFAllocatorDefault
, password
, kCFStringEncodingUTF8
, 0);
2502 CFDictionarySetValue(query
, kSecValueData
, credential
);
2503 CFReleaseSafe(credential
);
2504 CFDictionarySetValue(query
, kSecAttrComment
, kSecSafariDefaultComment
);
2506 CFReleaseSafe(swcclient
.accessGroups
);
2507 swcclient
.accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&kSecSafariAccessGroup
, 1, &kCFTypeArrayCallBacks
);
2509 // mark the item as created by this function
2510 const int32_t creator_value
= 'swca';
2511 CFNumberRef creator
= CFNumberCreate(kCFAllocatorDefault
, kCFNumberSInt32Type
, &creator_value
);
2513 CFDictionarySetValue(query
, kSecAttrCreator
, creator
);
2514 CFReleaseSafe(creator
);
2518 ok
= swca_confirm_operation(swca_add_request_id
, clientAuditToken
, query
, error
, ^void (CFStringRef fqdn
) {
2519 _SecAddNegativeWebCredential(client
, fqdn
, appID
, false);
2523 ok
= _SecItemAdd(query
, &swcclient
, result
, error
);
2527 CFReleaseSafe(attrs
);
2528 CFReleaseSafe(query
);
2529 CFReleaseSafe(swcclient
.accessGroups
);
2530 CFReleaseSafe(fqdn
);
2534 /* Specialized version of SecItemCopyMatching for shared web credentials */
2536 _SecCopySharedWebCredential(CFDictionaryRef query
,
2537 SecurityClient
*client
,
2538 const audit_token_t
*clientAuditToken
,
2544 CFMutableArrayRef credentials
= NULL
;
2545 CFMutableArrayRef foundItems
= NULL
;
2546 CFMutableArrayRef fqdns
= NULL
;
2547 CFStringRef fqdn
= NULL
;
2548 CFStringRef account
= NULL
;
2553 require_quiet(result
, cleanup
);
2554 credentials
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
2555 foundItems
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
2556 fqdns
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
2558 // give ourselves access to see matching items for kSecSafariAccessGroup
2559 CFStringRef accessGroup
= CFSTR("*");
2560 SecurityClient swcclient
= {
2562 .accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&accessGroup
, 1, &kCFTypeArrayCallBacks
),
2563 .allowSystemKeychain
= false,
2564 .allowSyncBubbleKeychain
= false,
2565 .isNetworkExtension
= false,
2566 .musr
= client
->musr
,
2569 // On input, the query dictionary contains optional fqdn and account entries.
2570 fqdn
= CFDictionaryGetValue(query
, kSecAttrServer
);
2571 account
= CFDictionaryGetValue(query
, kSecAttrAccount
);
2573 // Check autofill enabled status
2574 if (!swca_autofill_enabled(clientAuditToken
)) {
2575 SecError(errSecBadReq
, error
, CFSTR("Autofill is not enabled in Safari settings"));
2579 // Check fqdn; if NULL, add domains from caller's entitlement.
2581 CFArrayAppendValue(fqdns
, fqdn
);
2584 CFIndex idx
, count
= CFArrayGetCount(domains
);
2585 for (idx
=0; idx
< count
; idx
++) {
2586 CFStringRef str
= (CFStringRef
) CFArrayGetValueAtIndex(domains
, idx
);
2587 // Parse the entry for our service label prefix
2588 if (str
&& CFStringHasPrefix(str
, kSecSharedWebCredentialsService
)) {
2589 CFIndex prefix_len
= CFStringGetLength(kSecSharedWebCredentialsService
)+1;
2590 CFIndex substr_len
= CFStringGetLength(str
) - prefix_len
;
2591 CFRange range
= { prefix_len
, substr_len
};
2592 fqdn
= CFStringCreateWithSubstring(kCFAllocatorDefault
, str
, range
);
2594 CFArrayAppendValue(fqdns
, fqdn
);
2600 count
= CFArrayGetCount(fqdns
);
2602 SecError(errSecParam
, error
, CFSTR("No domain provided"));
2606 // Aggregate search results for each domain
2607 for (idx
= 0; idx
< count
; idx
++) {
2608 CFMutableArrayRef items
= NULL
;
2609 CFMutableDictionaryRef attrs
= NULL
;
2610 fqdn
= (CFStringRef
) CFArrayGetValueAtIndex(fqdns
, idx
);
2614 // Parse the fqdn for a possible port specifier.
2616 CFStringRef urlStr
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%@%@"), kSecSharedCredentialUrlScheme
, fqdn
);
2618 CFURLRef url
= CFURLCreateWithString(kCFAllocatorDefault
, urlStr
, nil
);
2620 CFStringRef hostname
= CFURLCopyHostName(url
);
2622 CFReleaseSafe(fqdn
);
2624 port
= CFURLGetPortNumber(url
);
2628 CFReleaseSafe(urlStr
);
2632 #if TARGET_IPHONE_SIMULATOR
2633 secerror("app/site association entitlements not checked in Simulator");
2635 OSStatus status
= errSecMissingEntitlement
;
2637 SecError(status
, error
, CFSTR("Missing application-identifier entitlement"));
2638 CFReleaseSafe(fqdn
);
2641 // validate that fqdn is part of caller's entitlement
2642 if (_SecEntitlementContainsDomainForService(domains
, fqdn
, kSecSharedWebCredentialsService
)) {
2643 status
= errSecSuccess
;
2645 if (errSecSuccess
!= status
) {
2646 CFStringRef msg
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
,
2647 CFSTR("%@ not found in %@ entitlement"), fqdn
, kSecEntitlementAssociatedDomains
);
2649 msg
= CFRetain(CFSTR("Requested domain not found in entitlement"));
2651 SecError(status
, error
, CFSTR("%@"), msg
);
2653 CFReleaseSafe(fqdn
);
2658 attrs
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2660 SecError(errSecAllocate
, error
, CFSTR("Unable to create query dictionary"));
2661 CFReleaseSafe(fqdn
);
2664 CFDictionaryAddValue(attrs
, kSecClass
, kSecClassInternetPassword
);
2665 CFDictionaryAddValue(attrs
, kSecAttrAccessGroup
, kSecSafariAccessGroup
);
2666 CFDictionaryAddValue(attrs
, kSecAttrProtocol
, kSecAttrProtocolHTTPS
);
2667 CFDictionaryAddValue(attrs
, kSecAttrAuthenticationType
, kSecAttrAuthenticationTypeHTMLForm
);
2668 CFDictionaryAddValue(attrs
, kSecAttrServer
, fqdn
);
2670 CFDictionaryAddValue(attrs
, kSecAttrAccount
, account
);
2672 if (port
< -1 || port
> 0) {
2673 SInt16 portValueShort
= (port
& 0xFFFF);
2674 CFNumberRef portNumber
= CFNumberCreate(NULL
, kCFNumberSInt16Type
, &portValueShort
);
2675 CFDictionaryAddValue(attrs
, kSecAttrPort
, portNumber
);
2676 CFReleaseSafe(portNumber
);
2678 CFDictionaryAddValue(attrs
, kSecAttrSynchronizable
, kCFBooleanTrue
);
2679 CFDictionaryAddValue(attrs
, kSecMatchLimit
, kSecMatchLimitAll
);
2680 CFDictionaryAddValue(attrs
, kSecReturnAttributes
, kCFBooleanTrue
);
2681 CFDictionaryAddValue(attrs
, kSecReturnData
, kCFBooleanTrue
);
2683 ok
= _SecItemCopyMatching(attrs
, &swcclient
, (CFTypeRef
*)&items
, error
);
2685 // ignore interim error since we have multiple domains to search
2686 CFReleaseNull(*error
);
2688 if (ok
&& items
&& CFGetTypeID(items
) == CFArrayGetTypeID()) {
2689 #if TARGET_IPHONE_SIMULATOR
2690 secerror("Ignoring app/site approval state in the Simulator.");
2691 bool approved
= true;
2693 // get approval status for this app/domain pair
2694 SWCFlags flags
= _SecAppDomainApprovalStatus(appID
, fqdn
, error
);
2696 // ignore interim error since we have multiple domains to check
2697 CFReleaseNull(*error
);
2699 bool approved
= (flags
& kSWCFlag_SiteApproved
);
2702 CFArrayAppendArray(foundItems
, items
, CFRangeMake(0, CFArrayGetCount(items
)));
2705 CFReleaseSafe(items
);
2706 CFReleaseSafe(attrs
);
2707 CFReleaseSafe(fqdn
);
2710 // If matching credentials are found, the credentials provided to the completionHandler
2711 // will be a CFArrayRef containing CFDictionaryRef entries. Each dictionary entry will
2712 // contain the following pairs (see Security/SecItem.h):
2713 // key: kSecAttrServer value: CFStringRef (the website)
2714 // key: kSecAttrAccount value: CFStringRef (the account)
2715 // key: kSecSharedPassword value: CFStringRef (the password)
2717 // key: kSecAttrPort value: CFNumberRef (the port number, if non-standard for https)
2719 count
= CFArrayGetCount(foundItems
);
2720 for (idx
= 0; idx
< count
; idx
++) {
2721 CFDictionaryRef dict
= (CFDictionaryRef
) CFArrayGetValueAtIndex(foundItems
, idx
);
2722 CFMutableDictionaryRef newdict
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2723 if (newdict
&& dict
&& CFGetTypeID(dict
) == CFDictionaryGetTypeID()) {
2724 CFStringRef srvr
= CFDictionaryGetValue(dict
, kSecAttrServer
);
2725 CFStringRef acct
= CFDictionaryGetValue(dict
, kSecAttrAccount
);
2726 CFNumberRef pnum
= CFDictionaryGetValue(dict
, kSecAttrPort
);
2727 CFStringRef icmt
= CFDictionaryGetValue(dict
, kSecAttrComment
);
2728 CFDataRef data
= CFDictionaryGetValue(dict
, kSecValueData
);
2730 CFDictionaryAddValue(newdict
, kSecAttrServer
, srvr
);
2733 CFDictionaryAddValue(newdict
, kSecAttrAccount
, acct
);
2737 if (CFNumberGetValue(pnum
, kCFNumberSInt16Type
, &pval
) &&
2738 (pval
< -1 || pval
> 0)) {
2739 CFDictionaryAddValue(newdict
, kSecAttrPort
, pnum
);
2743 CFStringRef password
= CFStringCreateFromExternalRepresentation(kCFAllocatorDefault
, data
, kCFStringEncodingUTF8
);
2745 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH && !TARGET_OS_TV
2746 CFDictionaryAddValue(newdict
, kSecSharedPassword
, password
);
2748 CFDictionaryAddValue(newdict
, CFSTR("spwd"), password
);
2750 CFReleaseSafe(password
);
2753 if (icmt
&& CFEqual(icmt
, kSecSafariDefaultComment
)) {
2754 CFArrayInsertValueAtIndex(credentials
, 0, newdict
);
2756 CFArrayAppendValue(credentials
, newdict
);
2759 CFReleaseSafe(newdict
);
2766 // create a new array of dictionaries (without the actual password) for picker UI
2767 count
= CFArrayGetCount(credentials
);
2768 CFMutableArrayRef items
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
2769 for (idx
= 0; idx
< count
; idx
++) {
2770 CFDictionaryRef dict
= (CFDictionaryRef
) CFArrayGetValueAtIndex(credentials
, idx
);
2771 CFMutableDictionaryRef newdict
= CFDictionaryCreateMutableCopy(kCFAllocatorDefault
, 0, dict
);
2772 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH && !TARGET_OS_TV
2773 CFDictionaryRemoveValue(newdict
, kSecSharedPassword
);
2775 CFDictionaryRemoveValue(newdict
, CFSTR("spwd"));
2777 CFArrayAppendValue(items
, newdict
);
2778 CFReleaseSafe(newdict
);
2781 // prompt user to select one of the dictionary items
2782 CFDictionaryRef selected
= swca_copy_selected_dictionary(swca_select_request_id
,
2783 clientAuditToken
, items
, error
);
2785 // find the matching item in our credentials array
2786 CFStringRef srvr
= CFDictionaryGetValue(selected
, kSecAttrServer
);
2787 CFStringRef acct
= CFDictionaryGetValue(selected
, kSecAttrAccount
);
2788 CFNumberRef pnum
= CFDictionaryGetValue(selected
, kSecAttrPort
);
2789 for (idx
= 0; idx
< count
; idx
++) {
2790 CFDictionaryRef dict
= (CFDictionaryRef
) CFArrayGetValueAtIndex(credentials
, idx
);
2791 CFStringRef srvr1
= CFDictionaryGetValue(dict
, kSecAttrServer
);
2792 CFStringRef acct1
= CFDictionaryGetValue(dict
, kSecAttrAccount
);
2793 CFNumberRef pnum1
= CFDictionaryGetValue(dict
, kSecAttrPort
);
2795 if (!srvr
|| !srvr1
|| !CFEqual(srvr
, srvr1
)) continue;
2796 if (!acct
|| !acct1
|| !CFEqual(acct
, acct1
)) continue;
2797 if ((pnum
&& pnum1
) && !CFEqual(pnum
, pnum1
)) continue;
2800 CFReleaseSafe(selected
);
2807 CFReleaseSafe(items
);
2808 CFArrayRemoveAllValues(credentials
);
2809 if (selected
&& ok
) {
2810 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
2811 fqdn
= CFDictionaryGetValue(selected
, kSecAttrServer
);
2813 CFArrayAppendValue(credentials
, selected
);
2817 #if TARGET_OS_IOS && !TARGET_OS_BRIDGE && !TARGET_IPHONE_SIMULATOR
2818 // register confirmation with database
2819 CFRetainSafe(appID
);
2821 if (0 != SWCSetServiceFlags(kSecSharedWebCredentialsService
,
2822 appID
, fqdn
, kSWCFlag_ExternalMask
, kSWCFlag_UserApproved
,
2823 ^void(OSStatus inStatus
, SWCFlags inNewFlags
){
2824 CFReleaseSafe(appID
);
2825 CFReleaseSafe(fqdn
);
2828 // we didn't queue the block
2829 CFReleaseSafe(appID
);
2830 CFReleaseSafe(fqdn
);
2834 CFReleaseSafe(selected
);
2836 else if (NULL
== *error
) {
2837 // found no items, and we haven't already filled in the error
2838 SecError(errSecItemNotFound
, error
, CFSTR("no matching items found"));
2843 CFArrayRemoveAllValues(credentials
);
2844 CFReleaseNull(credentials
);
2846 CFReleaseSafe(foundItems
);
2847 *result
= credentials
;
2848 CFReleaseSafe(swcclient
.accessGroups
);
2849 CFReleaseSafe(fqdns
);
2854 #endif /* TARGET_OS_IOS */
2858 // MARK: Keychain backup
2860 CF_RETURNS_RETAINED CFDataRef
2861 _SecServerKeychainCreateBackup(SecurityClient
*client
, CFDataRef keybag
, CFDataRef passcode
, bool emcs
, CFErrorRef
*error
) {
2862 __block CFDataRef backup
;
2863 kc_with_dbt(true, error
, ^bool (SecDbConnectionRef dbt
) {
2867 if (keybag
== NULL
&& passcode
== NULL
) {
2869 backup
= SecServerExportBackupableKeychain(dbt
, client
, KEYBAG_DEVICE
, backup_keybag_handle
, error
);
2870 #else /* !USE_KEYSTORE */
2872 SecError(errSecParam
, error
, CFSTR("Why are you doing this?"));
2874 #endif /* USE_KEYSTORE */
2876 backup
= SecServerKeychainCreateBackup(dbt
, client
, keybag
, passcode
, emcs
, error
);
2878 return (backup
!= NULL
);
2885 _SecServerKeychainRestore(CFDataRef backup
, SecurityClient
*client
, CFDataRef keybag
, CFDataRef passcode
, CFErrorRef
*error
) {
2886 if (backup
== NULL
|| keybag
== NULL
)
2887 return SecError(errSecParam
, error
, CFSTR("backup or keybag missing"));
2889 __block
bool ok
= true;
2890 ok
&= kc_with_dbt(true, error
, ^bool (SecDbConnectionRef dbconn
) {
2891 return SecServerKeychainRestore(dbconn
, client
, backup
, keybag
, passcode
, error
);
2895 SecKeychainChanged();
2902 _SecServerBackupCopyUUID(CFDataRef data
, CFErrorRef
*error
)
2904 CFStringRef uuid
= NULL
;
2905 CFDictionaryRef backup
;
2907 backup
= CFPropertyListCreateWithData(kCFAllocatorDefault
, data
,
2908 kCFPropertyListImmutable
, NULL
,
2910 if (isDictionary(backup
)) {
2911 uuid
= SecServerBackupGetKeybagUUID(backup
, error
);
2915 CFReleaseNull(backup
);
2923 // MARK: SecItemDataSource
2925 // Make sure to call this before any writes to the keychain, so that we fire
2926 // up the engines to monitor manifest changes.
2927 SOSDataSourceFactoryRef
SecItemDataSourceFactoryGetDefault(void) {
2928 return SecItemDataSourceFactoryGetShared(kc_dbhandle(NULL
));
2931 /* AUDIT[securityd]:
2932 args_in (ok) is a caller provided, CFDictionaryRef.
2935 CF_RETURNS_RETAINED CFArrayRef
2936 _SecServerKeychainSyncUpdateMessage(CFDictionaryRef updates
, CFErrorRef
*error
) {
2937 // This never fails, trust us!
2938 return SOSCCHandleUpdateMessage(updates
);
2942 // Truthiness in the cloud backup/restore support.
2945 static CFDictionaryRef
2946 _SecServerCopyTruthInTheCloud(CFDataRef keybag
, CFDataRef password
,
2947 CFDictionaryRef backup
, CFErrorRef
*error
)
2949 SOSManifestRef mold
= NULL
, mnow
= NULL
, mdelete
= NULL
, madd
= NULL
;
2950 __block CFMutableDictionaryRef backup_new
= NULL
;
2951 keybag_handle_t bag_handle
;
2952 if (!ks_open_keybag(keybag
, password
, &bag_handle
, error
))
2955 // We need to have a datasource singleton for protection domain
2956 // kSecAttrAccessibleWhenUnlocked and keep a single shared engine
2957 // instance around which we create in the datasource constructor as well.
2958 SOSDataSourceFactoryRef dsf
= SecItemDataSourceFactoryGetDefault();
2959 SOSDataSourceRef ds
= SOSDataSourceFactoryCreateDataSource(dsf
, kSecAttrAccessibleWhenUnlocked
, error
);
2961 backup_new
= backup
? CFDictionaryCreateMutableCopy(kCFAllocatorDefault
, 0, backup
) : CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2962 mold
= SOSCreateManifestWithBackup(backup
, error
);
2963 SOSEngineRef engine
= SOSDataSourceGetSharedEngine(ds
, error
);
2964 mnow
= SOSEngineCopyManifest(engine
, NULL
);
2966 mnow
= SOSDataSourceCopyManifestWithViewNameSet(ds
, SOSViewsGetV0ViewSet(), error
);
2969 CFReleaseNull(backup_new
);
2970 secerror("failed to obtain manifest for keychain: %@", error
? *error
: NULL
);
2972 SOSManifestDiff(mold
, mnow
, &mdelete
, &madd
, error
);
2975 // Delete everything from the new_backup that is no longer in the datasource according to the datasources manifest.
2976 SOSManifestForEach(mdelete
, ^(CFDataRef digest_data
, bool *stop
) {
2977 CFStringRef deleted_item_key
= CFDataCopyHexString(digest_data
);
2978 CFDictionaryRemoveValue(backup_new
, deleted_item_key
);
2979 CFRelease(deleted_item_key
);
2982 CFMutableArrayRef changes
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
2983 SOSDataSourceForEachObject(ds
, NULL
, madd
, error
, ^void(CFDataRef digest
, SOSObjectRef object
, bool *stop
) {
2984 CFErrorRef localError
= NULL
;
2985 CFDataRef digest_data
= NULL
;
2986 CFTypeRef value
= NULL
;
2988 // Key in our manifest can't be found in db, remove it from our manifest
2989 SOSChangesAppendDelete(changes
, digest
);
2990 } else if (!(digest_data
= SOSObjectCopyDigest(ds
, object
, &localError
))
2991 || !(value
= SOSObjectCopyBackup(ds
, object
, bag_handle
, &localError
))) {
2992 if (SecErrorGetOSStatus(localError
) == errSecDecode
) {
2993 // Ignore decode errors, pretend the objects aren't there
2994 CFRelease(localError
);
2995 // Object undecodable, remove it from our manifest
2996 SOSChangesAppendDelete(changes
, digest
);
2998 // Stop iterating and propagate out all other errors.
3000 *error
= localError
;
3001 CFReleaseNull(backup_new
);
3004 // TODO: Should we skip tombstones here?
3005 CFStringRef key
= CFDataCopyHexString(digest_data
);
3006 CFDictionarySetValue(backup_new
, key
, value
);
3009 CFReleaseSafe(digest_data
);
3010 CFReleaseSafe(value
);
3011 }) || CFReleaseNull(backup_new
);
3013 if (CFArrayGetCount(changes
)) {
3014 if (!SOSEngineUpdateChanges(engine
, kSOSDataSourceSOSTransaction
, changes
, error
)) {
3015 CFReleaseNull(backup_new
);
3018 CFReleaseSafe(changes
);
3020 SOSDataSourceRelease(ds
, error
) || CFReleaseNull(backup_new
);
3023 CFReleaseSafe(mold
);
3024 CFReleaseSafe(mnow
);
3025 CFReleaseSafe(madd
);
3026 CFReleaseSafe(mdelete
);
3027 ks_close_keybag(bag_handle
, error
) || CFReleaseNull(backup_new
);
3033 _SecServerRestoreTruthInTheCloud(CFDataRef keybag
, CFDataRef password
, CFDictionaryRef backup_in
, CFErrorRef
*error
) {
3034 __block
bool ok
= true;
3035 keybag_handle_t bag_handle
;
3036 if (!ks_open_keybag(keybag
, password
, &bag_handle
, error
))
3039 SOSManifestRef mbackup
= SOSCreateManifestWithBackup(backup_in
, error
);
3041 SOSDataSourceFactoryRef dsf
= SecItemDataSourceFactoryGetDefault();
3042 SOSDataSourceRef ds
= SOSDataSourceFactoryCreateDataSource(dsf
, kSecAttrAccessibleWhenUnlocked
, error
);
3043 ok
&= ds
&& SOSDataSourceWith(ds
, error
, ^(SOSTransactionRef txn
, bool *commit
) {
3044 SOSManifestRef mnow
= SOSDataSourceCopyManifestWithViewNameSet(ds
, SOSViewsGetV0BackupViewSet(), error
);
3045 SOSManifestRef mdelete
= NULL
, madd
= NULL
;
3046 SOSManifestDiff(mnow
, mbackup
, &mdelete
, &madd
, error
);
3048 // Don't delete everything in datasource not in backup.
3050 // Add items from the backup
3051 SOSManifestForEach(madd
, ^void(CFDataRef e
, bool *stop
) {
3052 CFDictionaryRef item
= NULL
;
3053 CFStringRef sha1
= CFDataCopyHexString(e
);
3055 item
= CFDictionaryGetValue(backup_in
, sha1
);
3059 CFErrorRef localError
= NULL
;
3061 if (!SOSObjectRestoreObject(ds
, txn
, bag_handle
, item
, &localError
)) {
3062 OSStatus status
= SecErrorGetOSStatus(localError
);
3063 if (status
== errSecDuplicateItem
) {
3064 // Log and ignore duplicate item errors during restore
3065 secnotice("titc", "restore %@ not replacing existing item", item
);
3066 } else if (status
== errSecDecode
) {
3067 // Log and ignore corrupted item errors during restore
3068 secnotice("titc", "restore %@ skipping corrupted item %@", item
, localError
);
3070 if (status
== errSecInteractionNotAllowed
)
3072 // Propagate the first other error upwards (causing the restore to fail).
3073 secerror("restore %@ failed %@", item
, localError
);
3075 if (error
&& !*error
) {
3076 *error
= localError
;
3080 CFReleaseSafe(localError
);
3084 ok
&= SOSDataSourceRelease(ds
, error
);
3085 CFReleaseNull(mdelete
);
3086 CFReleaseNull(madd
);
3087 CFReleaseNull(mnow
);
3092 ok
&= ks_close_keybag(bag_handle
, error
);
3098 CF_RETURNS_RETAINED CFDictionaryRef
3099 _SecServerBackupSyncable(CFDictionaryRef backup
, CFDataRef keybag
, CFDataRef password
, CFErrorRef
*error
) {
3100 require_action_quiet(isData(keybag
), errOut
, SecError(errSecParam
, error
, CFSTR("keybag %@ not a data"), keybag
));
3101 require_action_quiet(!backup
|| isDictionary(backup
), errOut
, SecError(errSecParam
, error
, CFSTR("backup %@ not a dictionary"), backup
));
3102 require_action_quiet(!password
|| isData(password
), errOut
, SecError(errSecParam
, error
, CFSTR("password %@ not a data"), password
));
3104 return _SecServerCopyTruthInTheCloud(keybag
, password
, backup
, error
);
3111 _SecServerRestoreSyncable(CFDictionaryRef backup
, CFDataRef keybag
, CFDataRef password
, CFErrorRef
*error
) {
3113 require_action_quiet(isData(keybag
), errOut
, ok
= SecError(errSecParam
, error
, CFSTR("keybag %@ not a data"), keybag
));
3114 require_action_quiet(isDictionary(backup
), errOut
, ok
= SecError(errSecParam
, error
, CFSTR("backup %@ not a dictionary"), backup
));
3117 require_action_quiet(isData(password
), errOut
, ok
= SecError(errSecParam
, error
, CFSTR("password not a data")));
3120 ok
= _SecServerRestoreTruthInTheCloud(keybag
, password
, backup
, error
);
3126 bool _SecServerRollKeysGlue(bool force
, CFErrorRef
*error
) {
3127 return _SecServerRollKeys(force
, NULL
, error
);
3131 bool _SecServerRollKeys(bool force
, SecurityClient
*client
, CFErrorRef
*error
) {
3133 uint32_t keystore_generation_status
= 0;
3134 if (aks_generation(KEYBAG_DEVICE
, generation_noop
, &keystore_generation_status
))
3136 uint32_t current_generation
= keystore_generation_status
& generation_current
;
3138 return kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
3139 bool up_to_date
= s3dl_dbt_keys_current(dbt
, current_generation
, NULL
);
3141 if (force
&& !up_to_date
) {
3142 up_to_date
= s3dl_dbt_update_keys(dbt
, client
, error
);
3144 secerror("Completed roll keys.");
3145 up_to_date
= s3dl_dbt_keys_current(dbt
, current_generation
, NULL
);
3148 secerror("Failed to roll keys.");
3158 InitialSyncItems(CFMutableArrayRef items
, bool limitToCurrent
, CFStringRef agrp
, CFStringRef svce
, const SecDbClass
*qclass
, CFErrorRef
*error
)
3160 bool result
= false;
3163 q
= query_create(qclass
, NULL
, NULL
, error
);
3166 q
->q_return_type
= kSecReturnDataMask
| kSecReturnAttributesMask
;
3167 q
->q_limit
= kSecMatchUnlimited
;
3168 q
->q_keybag
= KEYBAG_DEVICE
;
3170 query_add_attribute(kSecAttrAccessGroup
, agrp
, q
);
3171 query_add_attribute(kSecAttrSynchronizable
, kCFBooleanTrue
, q
);
3172 query_add_attribute(kSecAttrTombstone
, kCFBooleanFalse
, q
);
3174 query_add_attribute(kSecAttrService
, svce
, q
);
3176 result
= kc_with_dbt(false, error
, ^(SecDbConnectionRef dbt
) {
3177 return kc_transaction(dbt
, error
, ^{
3178 CFErrorRef error2
= NULL
;
3180 SecDbItemSelect(q
, dbt
, &error2
, NULL
, ^bool(const SecDbAttr
*attr
) {
3181 return CFDictionaryGetValue(q
->q_item
, attr
->name
);
3182 }, NULL
, NULL
, ^(SecDbItemRef item
, bool *stop
) {
3183 CFErrorRef error3
= NULL
;
3184 secinfo("InitialSyncItems", "Copy item");
3186 CFMutableDictionaryRef attrs
= SecDbItemCopyPListWithMask(item
, kSecDbSyncFlag
, &error3
);
3189 CFStringRef itemvwht
= CFDictionaryGetValue(attrs
, kSecAttrSyncViewHint
);
3191 * Saying its a SOS viewhint is really not the right answer post Triangle
3193 if (isString(itemvwht
) && !SOSViewInSOSSystem(itemvwht
)) {
3197 * Here we encode how PCS stores identities so that we only copy the
3198 * current identites for performance reasons.
3200 if (limitToCurrent
) {
3201 enum { PCS_CURRENT_IDENTITY_OFFSET
= 0x10000 };
3204 CFNumberRef type
= CFDictionaryGetValue(attrs
, kSecAttrType
);
3205 if (!isNumber(type
)) {
3206 // still allow this case since its not a service identity ??
3207 } else if (!CFNumberGetValue(type
, kCFNumberSInt32Type
, &s32
)) {
3209 } else if ((s32
& PCS_CURRENT_IDENTITY_OFFSET
) == 0) {
3214 CFDictionaryAddValue(attrs
, kSecClass
, SecDbItemGetClass(item
)->name
);
3215 CFArrayAppendValue(items
, attrs
);
3218 CFReleaseNull(attrs
);
3220 CFReleaseNull(error3
);
3222 CFReleaseNull(error2
);
3230 query_destroy(q
, NULL
);
3235 _SecServerCopyInitialSyncCredentials(uint32_t flags
, CFErrorRef
*error
)
3237 CFMutableArrayRef items
= CFArrayCreateMutableForCFTypes(NULL
);
3239 if (flags
& SecServerInitialSyncCredentialFlagTLK
) {
3240 require_action(InitialSyncItems(items
, false, CFSTR("com.apple.security.ckks"), NULL
, inet_class(), error
), fail
,
3241 secerror("failed to collect CKKS-inet keys: %@", error
? *error
: NULL
));
3243 if (flags
& SecServerInitialSyncCredentialFlagPCS
) {
3244 bool onlyCurrent
= !(flags
& SecServerInitialSyncCredentialFlagPCSNonCurrent
);
3246 require_action(InitialSyncItems(items
, false, CFSTR("com.apple.ProtectedCloudStorage"), NULL
, genp_class(), error
), fail
,
3247 secerror("failed to collect PCS-genp keys: %@", error
? *error
: NULL
));
3248 require_action(InitialSyncItems(items
, onlyCurrent
, CFSTR("com.apple.ProtectedCloudStorage"), NULL
, inet_class(), error
), fail
,
3249 secerror("failed to collect PCS-inet keys: %@", error
? *error
: NULL
));
3251 if (flags
& SecServerInitialSyncCredentialFlagBluetoothMigration
) {
3252 require_action(InitialSyncItems(items
, false, CFSTR("com.apple.nanoregistry.migration"), NULL
, genp_class(), error
), fail
,
3253 secerror("failed to collect com.apple.nanoregistry.migration-genp item: %@", error
? *error
: NULL
));
3254 require_action(InitialSyncItems(items
, false, CFSTR("com.apple.nanoregistry.migration2"), NULL
, genp_class(), error
), fail
,
3255 secerror("failed to collect com.apple.nanoregistry.migration2-genp item: %@", error
? *error
: NULL
));
3256 require_action(InitialSyncItems(items
, false, CFSTR("com.apple.bluetooth"), CFSTR("BluetoothLESync"), genp_class(), error
), fail
,
3257 secerror("failed to collect com.apple.bluetooth-genp item: %@", error
? *error
: NULL
));
3266 _SecServerImportInitialSyncCredentials(CFArrayRef array
, CFErrorRef
*error
)
3268 return kc_with_dbt(true, error
, ^bool(SecDbConnectionRef dbt
) {
3269 return kc_transaction(dbt
, error
, ^bool(void){
3270 CFIndex n
, count
= CFArrayGetCount(array
);
3272 secinfo("ImportInitialSyncItems", "Importing %d items", (int)count
);
3274 for (n
= 0; n
< count
; n
++) {
3275 CFErrorRef cferror
= NULL
;
3277 CFDictionaryRef item
= CFArrayGetValueAtIndex(array
, n
);
3278 if (!isDictionary(item
))
3281 CFStringRef className
= CFDictionaryGetValue(item
, kSecClass
);
3282 if (className
== NULL
) {
3283 secinfo("ImportInitialSyncItems", "Item w/o class");
3287 const SecDbClass
*cls
= kc_class_with_name(className
);
3289 secinfo("ImportInitialSyncItems", "Item with unknown class: %@", className
);
3293 SecDbItemRef dbi
= SecDbItemCreateWithAttributes(NULL
, cls
, item
, KEYBAG_DEVICE
, &cferror
);
3295 secinfo("ImportInitialSyncItems", "Item creation failed with: %@", cferror
);
3296 CFReleaseNull(cferror
);
3300 if (!SecDbItemSetSyncable(dbi
, true, &cferror
)) {
3301 secinfo("ImportInitialSyncItems", "Failed to set sync=1: %@ for item %@", cferror
, dbi
);
3302 CFReleaseNull(cferror
);
3307 if (!SecDbItemInsert(dbi
, dbt
, &cferror
)) {
3308 secinfo("ImportInitialSyncItems", "Item store failed with: %@: %@", cferror
, dbi
);
3309 CFReleaseNull(cferror
);
3323 * Sync bubble migration code
3326 struct SyncBubbleRule
{
3327 CFStringRef attribute
;
3332 TransmogrifyItemsToSyncBubble(SecurityClient
*client
, uid_t uid
,
3335 const SecDbClass
*qclass
,
3336 struct SyncBubbleRule
*items
, CFIndex nItems
,
3339 CFMutableDictionaryRef updateAttributes
= NULL
;
3340 CFDataRef syncBubbleView
= NULL
;
3341 CFDataRef activeUserView
= NULL
;
3346 syncBubbleView
= SecMUSRCreateSyncBubbleUserUUID(uid
);
3347 require(syncBubbleView
, fail
);
3349 activeUserView
= SecMUSRCreateActiveUserUUID(uid
);
3350 require(activeUserView
, fail
);
3353 if ((onlyDelete
&& !copyToo
) || !onlyDelete
) {
3356 * Clean out items first
3359 secnotice("syncbubble", "cleaning out old items");
3361 q
= query_create(qclass
, NULL
, NULL
, error
);
3364 q
->q_limit
= kSecMatchUnlimited
;
3365 q
->q_keybag
= device_keybag_handle
;
3367 for (n
= 0; n
< nItems
; n
++) {
3368 query_add_attribute(items
[n
].attribute
, items
[n
].value
, q
);
3370 q
->q_musrView
= CFRetain(syncBubbleView
);
3371 require(q
->q_musrView
, fail
);
3373 kc_with_dbt(false, error
, ^(SecDbConnectionRef dbt
) {
3374 return kc_transaction(dbt
, error
, ^{
3375 return s3dl_query_delete(dbt
, q
, NULL
, error
);
3379 query_destroy(q
, NULL
);
3384 if (onlyDelete
|| !copyToo
) {
3385 secnotice("syncbubble", "skip migration of items");
3388 * Copy over items from EMCS to sync bubble
3391 secnotice("syncbubble", "migrating sync bubble items");
3393 q
= query_create(qclass
, NULL
, NULL
, error
);
3396 q
->q_return_type
= kSecReturnDataMask
| kSecReturnAttributesMask
;
3397 q
->q_limit
= kSecMatchUnlimited
;
3398 q
->q_keybag
= device_keybag_handle
; /* XXX change to session key bag when it exists */
3400 for (n
= 0; n
< nItems
; n
++) {
3401 query_add_or_attribute(items
[n
].attribute
, items
[n
].value
, q
);
3403 query_add_or_attribute(CFSTR("musr"), activeUserView
, q
);
3404 q
->q_musrView
= CFRetain(activeUserView
);
3406 updateAttributes
= CFDictionaryCreateMutableForCFTypes(NULL
);
3407 require(updateAttributes
, fail
);
3409 CFDictionarySetValue(updateAttributes
, CFSTR("musr"), syncBubbleView
); /* XXX should use kSecAttrMultiUser */
3412 kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
3413 return kc_transaction(dbt
, error
, ^{
3414 CFErrorRef error2
= NULL
;
3416 SecDbItemSelect(q
, dbt
, &error2
, NULL
, ^bool(const SecDbAttr
*attr
) {
3417 return CFDictionaryGetValue(q
->q_item
, attr
->name
);
3418 }, NULL
, NULL
, ^(SecDbItemRef item
, bool *stop
) {
3419 CFErrorRef error3
= NULL
;
3420 secinfo("syncbubble", "migrating item");
3422 SecDbItemRef new_item
= SecDbItemCopyWithUpdates(item
, updateAttributes
, NULL
);
3423 if (new_item
== NULL
)
3426 SecDbItemClearRowId(new_item
, NULL
);
3428 if (!SecDbItemSetKeybag(new_item
, device_keybag_handle
, NULL
)) {
3429 CFRelease(new_item
);
3433 if (!SecDbItemInsert(new_item
, dbt
, &error3
)) {
3434 secnotice("syncbubble", "migration failed with %@ for item %@", error3
, new_item
);
3436 CFRelease(new_item
);
3437 CFReleaseNull(error3
);
3439 CFReleaseNull(error2
);
3448 CFReleaseNull(syncBubbleView
);
3449 CFReleaseNull(activeUserView
);
3450 CFReleaseNull(updateAttributes
);
3452 query_destroy(q
, NULL
);
3457 static struct SyncBubbleRule PCSItems
[] = {
3459 .attribute
= CFSTR("agrp"),
3460 .value
= CFSTR("com.apple.ProtectedCloudStorage"),
3463 static struct SyncBubbleRule NSURLSesssiond
[] = {
3465 .attribute
= CFSTR("agrp"),
3466 .value
= CFSTR("com.apple.nsurlsessiond"),
3469 static struct SyncBubbleRule AccountsdItems
[] = {
3471 .attribute
= CFSTR("svce"),
3472 .value
= CFSTR("com.apple.account.AppleAccount.token"),
3475 .attribute
= CFSTR("svce"),
3476 .value
= CFSTR("com.apple.account.AppleAccount.password"),
3479 .attribute
= CFSTR("svce"),
3480 .value
= CFSTR("com.apple.account.AppleAccount.rpassword"),
3483 .attribute
= CFSTR("svce"),
3484 .value
= CFSTR("com.apple.account.idms.token"),
3487 .attribute
= CFSTR("svce"),
3488 .value
= CFSTR("com.apple.account.idms.continuation-key"),
3491 .attribute
= CFSTR("svce"),
3492 .value
= CFSTR("com.apple.account.CloudKit.token"),
3496 static struct SyncBubbleRule MobileMailItems
[] = {
3498 .attribute
= CFSTR("svce"),
3499 .value
= CFSTR("com.apple.account.IMAP.password"),
3502 .attribute
= CFSTR("svce"),
3503 .value
= CFSTR("com.apple.account.SMTP.password"),
3506 .attribute
= CFSTR("svce"),
3507 .value
= CFSTR("com.apple.account.Exchange.password"),
3510 .attribute
= CFSTR("svce"),
3511 .value
= CFSTR("com.apple.account.Hotmail.password"),
3514 .attribute
= CFSTR("svce"),
3515 .value
= CFSTR("com.apple.account.Google.password"),
3518 .attribute
= CFSTR("svce"),
3519 .value
= CFSTR("com.apple.account.Google.oauth-token"),
3522 .attribute
= CFSTR("svce"),
3523 .value
= CFSTR("com.apple.account.Google.oath-refresh-token"),
3526 .attribute
= CFSTR("svce"),
3527 .value
= CFSTR("com.apple.account.Yahoo.password"),
3530 .attribute
= CFSTR("svce"),
3531 .value
= CFSTR("com.apple.account.Yahoo.oauth-token"),
3534 .attribute
= CFSTR("svce"),
3535 .value
= CFSTR("com.apple.account.Yahoo.oauth-token-nosync"),
3538 .attribute
= CFSTR("svce"),
3539 .value
= CFSTR("com.apple.account.Yahoo.oath-refresh-token"),
3542 .attribute
= CFSTR("svce"),
3543 .value
= CFSTR("com.apple.account.IMAPNotes.password"),
3546 .attribute
= CFSTR("svce"),
3547 .value
= CFSTR("com.apple.account.IMAPMail.password"),
3550 .attribute
= CFSTR("svce"),
3551 .value
= CFSTR("com.apple.account.126.password"),
3554 .attribute
= CFSTR("svce"),
3555 .value
= CFSTR("com.apple.account.163.password"),
3558 .attribute
= CFSTR("svce"),
3559 .value
= CFSTR("com.apple.account.aol.password"),
3564 ArrayContains(CFArrayRef array
, CFStringRef service
)
3566 return CFArrayContainsValue(array
, CFRangeMake(0, CFArrayGetCount(array
)), service
);
3570 _SecServerTransmogrifyToSyncBubble(CFArrayRef services
, uid_t uid
, SecurityClient
*client
, CFErrorRef
*error
)
3572 bool copyCloudAuthToken
= false;
3573 bool copyMobileMail
= false;
3575 bool copyPCS
= false;
3576 bool onlyDelete
= false;
3577 bool copyNSURLSesssion
= false;
3579 if (!client
->inMultiUser
)
3582 secnotice("syncbubble", "migration for uid %d uid for services %@", (int)uid
, services
);
3584 #if TARGET_OS_SIMULATOR
3587 if (uid
!= (uid_t
)client
->activeUser
)
3590 #error "no sync bubble on other platforms"
3594 * First select that services to copy/delete
3597 if (ArrayContains(services
, CFSTR("com.apple.bird.usermanager.sync"))
3598 || ArrayContains(services
, CFSTR("com.apple.cloudphotod.sync"))
3599 || ArrayContains(services
, CFSTR("com.apple.cloudphotod.syncstakeholder"))
3600 || ArrayContains(services
, CFSTR("com.apple.cloudd.usermanager.sync")))
3602 copyCloudAuthToken
= true;
3606 if (ArrayContains(services
, CFSTR("com.apple.nsurlsessiond.usermanager.sync")))
3608 copyCloudAuthToken
= true;
3609 copyNSURLSesssion
= true;
3612 if (ArrayContains(services
, CFSTR("com.apple.syncdefaultsd.usermanager.sync"))) {
3613 copyCloudAuthToken
= true;
3615 if (ArrayContains(services
, CFSTR("com.apple.mailq.sync")) || ArrayContains(services
, CFSTR("com.apple.mailq.sync.xpc"))) {
3616 copyCloudAuthToken
= true;
3617 copyMobileMail
= true;
3622 * The actually copy/delete the items selected
3625 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyPCS
, inet_class(), PCSItems
, sizeof(PCSItems
)/sizeof(PCSItems
[0]), error
);
3627 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyPCS
, genp_class(), PCSItems
, sizeof(PCSItems
)/sizeof(PCSItems
[0]), error
);
3631 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyMobileMail
, genp_class(), MobileMailItems
, sizeof(MobileMailItems
)/sizeof(MobileMailItems
[0]), error
);
3635 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyCloudAuthToken
, genp_class(), AccountsdItems
, sizeof(AccountsdItems
)/sizeof(AccountsdItems
[0]), error
);
3639 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyNSURLSesssion
, inet_class(), NSURLSesssiond
, sizeof(NSURLSesssiond
)/sizeof(NSURLSesssiond
[0]), error
);
3647 * Migrate from user keychain to system keychain when switching to edu mode
3651 _SecServerTransmogrifyToSystemKeychain(SecurityClient
*client
, CFErrorRef
*error
)
3653 __block
bool ok
= true;
3656 * we are not in multi user yet, about to switch, otherwise we would
3657 * check that for client->inMultiuser here
3660 kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
3661 return kc_transaction(dbt
, error
, ^{
3662 CFDataRef systemUUID
= SecMUSRGetSystemKeychainUUID();
3664 const SecDbSchema
*newSchema
= current_schema();
3665 SecDbClass
const *const *kcClass
;
3667 for (kcClass
= newSchema
->classes
; *kcClass
!= NULL
; kcClass
++) {
3668 CFErrorRef localError
= NULL
;
3671 if (!((*kcClass
)->itemclass
)) {
3675 q
= query_create(*kcClass
, SecMUSRGetSingleUserKeychainUUID(), NULL
, error
);
3679 ok
&= SecDbItemSelect(q
, dbt
, error
, ^bool(const SecDbAttr
*attr
) {
3680 return (attr
->flags
& kSecDbInFlag
) != 0;
3681 }, ^bool(const SecDbAttr
*attr
) {
3682 // No filtering please.
3684 }, ^bool(CFMutableStringRef sql
, bool *needWhere
) {
3685 SecDbAppendWhereOrAnd(sql
, needWhere
);
3686 CFStringAppendFormat(sql
, NULL
, CFSTR("musr = ?"));
3688 }, ^bool(sqlite3_stmt
*stmt
, int col
) {
3689 return SecDbBindObject(stmt
, col
++, SecMUSRGetSingleUserKeychainUUID(), error
);
3690 }, ^(SecDbItemRef item
, bool *stop
) {
3691 CFErrorRef localError
= NULL
;
3693 if (!SecDbItemSetValueWithName(item
, kSecAttrMultiUser
, systemUUID
, &localError
)) {
3694 secerror("item: %@ update musr to system failed: %@", item
, localError
);
3699 if (!SecDbItemDoUpdate(item
, item
, dbt
, &localError
, ^bool (const SecDbAttr
*attr
) {
3700 return attr
->kind
== kSecDbRowIdAttr
;
3702 secerror("item: %@ insert during UPDATE: %@", item
, localError
);
3708 SecErrorPropagate(localError
, error
);
3712 query_destroy(q
, &localError
);
3723 * Delete account from local usage
3727 _SecServerDeleteMUSERViews(SecurityClient
*client
, uid_t uid
, CFErrorRef
*error
)
3729 return kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
3730 CFDataRef musrView
= NULL
, syncBubbleView
= NULL
;
3733 syncBubbleView
= SecMUSRCreateSyncBubbleUserUUID(uid
);
3734 require(syncBubbleView
, fail
);
3736 musrView
= SecMUSRCreateActiveUserUUID(uid
);
3737 require(musrView
, fail
);
3739 require(ok
= SecServerDeleteAllForUser(dbt
, syncBubbleView
, false, error
), fail
);
3740 require(ok
= SecServerDeleteAllForUser(dbt
, musrView
, false, error
), fail
);
3743 CFReleaseNull(syncBubbleView
);
3744 CFReleaseNull(musrView
);
3750 #endif /* TARGET_OS_IOS */
3753 _SecServerGetKeyStats(const SecDbClass
*qclass
,
3754 struct _SecServerKeyStats
*stats
)
3756 __block CFErrorRef error
= NULL
;
3759 Query
*q
= query_create(qclass
, NULL
, NULL
, &error
);
3762 q
->q_return_type
= kSecReturnDataMask
| kSecReturnAttributesMask
;
3763 q
->q_limit
= kSecMatchUnlimited
;
3764 q
->q_keybag
= KEYBAG_DEVICE
;
3765 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleWhenUnlocked
, q
);
3766 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleAfterFirstUnlock
, q
);
3767 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleAlways
, q
);
3768 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleWhenUnlockedThisDeviceOnly
, q
);
3769 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
, q
);
3770 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleAlwaysThisDeviceOnly
, q
);
3771 query_add_attribute(kSecAttrTombstone
, kCFBooleanFalse
, q
);
3773 kc_with_dbt(false, &error
, ^(SecDbConnectionRef dbconn
) {
3774 CFErrorRef error2
= NULL
;
3775 __block CFIndex totalSize
= 0;
3776 stats
->maxDataSize
= 0;
3778 SecDbItemSelect(q
, dbconn
, &error2
, NULL
, ^bool(const SecDbAttr
*attr
) {
3779 return CFDictionaryContainsKey(q
->q_item
, attr
->name
);
3780 }, NULL
, NULL
, ^(SecDbItemRef item
, bool *stop
) {
3781 CFErrorRef error3
= NULL
;
3782 CFDataRef data
= SecDbItemGetValue(item
, &v6v_Data
, &error3
);
3784 CFIndex size
= CFDataGetLength(data
);
3785 if (size
> stats
->maxDataSize
)
3786 stats
->maxDataSize
= size
;
3790 CFReleaseNull(error3
);
3792 CFReleaseNull(error2
);
3794 stats
->averageSize
= totalSize
/ stats
->items
;
3803 CFReleaseNull(error
);
3805 query_destroy(q
, NULL
);
3809 CFArrayRef
_SecItemCopyParentCertificates(CFDataRef normalizedIssuer
, CFArrayRef accessGroups
, CFErrorRef
*error
) {
3810 const void *keys
[] = {
3817 kSecClassCertificate
,
3822 CFDictionaryRef query
= CFDictionaryCreate(NULL
, keys
, values
, 4,
3824 CFTypeRef results
= NULL
;
3825 SecurityClient client
= {
3827 .accessGroups
= accessGroups
,
3828 .allowSystemKeychain
= true,
3829 .allowSyncBubbleKeychain
= false,
3830 .isNetworkExtension
= false,
3833 (void)_SecItemCopyMatching(query
, &client
, &results
, error
);
3838 bool _SecItemCertificateExists(CFDataRef normalizedIssuer
, CFDataRef serialNumber
, CFArrayRef accessGroups
, CFErrorRef
*error
) {
3839 const void *keys
[] = {
3843 kSecAttrSerialNumber
3846 kSecClassCertificate
,
3851 SecurityClient client
= {
3853 .accessGroups
= accessGroups
,
3854 .allowSystemKeychain
= true,
3855 .allowSyncBubbleKeychain
= false,
3856 .isNetworkExtension
= false,
3858 CFDictionaryRef query
= CFDictionaryCreate(NULL
, keys
, values
, 4, NULL
, NULL
);
3859 CFTypeRef results
= NULL
;
3860 bool ok
= _SecItemCopyMatching(query
, &client
, &results
, error
);
3861 CFReleaseSafe(query
);
3862 CFReleaseSafe(results
);