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 <securityd/SOSCloudCircleServer.h>
37 #include <Security/SecBasePriv.h>
38 #include <Security/SecItemPriv.h>
39 #include <Security/SecItemInternal.h>
40 #include <Security/SecureObjectSync/SOSChangeTracker.h>
41 #include <Security/SecureObjectSync/SOSDigestVector.h>
42 #include <Security/SecureObjectSync/SOSEngine.h>
43 #include <Security/SecureObjectSync/SOSViews.h>
44 #include <Security/SecTrustPriv.h>
45 #include <Security/SecTrustInternal.h>
46 #include <Security/SecCertificatePriv.h>
47 #include <Security/SecEntitlements.h>
49 #include <keychain/ckks/CKKS.h>
52 #include <MobileKeyBag/MobileKeyBag.h>
53 #include <Kernel/IOKit/crypto/AppleKeyStoreDefs.h>
55 // TODO: Make this include work on both platforms. rdar://problem/16526848
56 #if TARGET_OS_EMBEDDED
57 #include <Security/SecEntitlements.h>
59 /* defines from <Security/SecEntitlements.h> */
60 #define kSecEntitlementAssociatedDomains CFSTR("com.apple.developer.associated-domains")
61 #define kSecEntitlementPrivateAssociatedDomains CFSTR("com.apple.private.associated-domains")
64 #if TARGET_OS_EMBEDDED
65 #include <utilities/SecADWrapper.h>
70 #include <utilities/array_size.h>
71 #include <utilities/SecFileLocations.h>
72 #include <utilities/SecTrace.h>
73 #include <utilities/SecXPCError.h>
74 #include <utilities/sec_action.h>
75 #include <Security/SecuritydXPC.h>
76 #include "swcagent_client.h"
77 #include "SecPLWrappers.h"
79 #if TARGET_OS_IOS && !TARGET_OS_BRIDGE
80 #include <SharedWebCredentials/SharedWebCredentials.h>
83 /* Changed the name of the keychain changed notification, for testing */
84 static const char *g_keychain_changed_notification
= kSecServerKeychainChangedNotification
;
86 void SecItemServerSetKeychainChangedNotification(const char *notification_name
)
88 g_keychain_changed_notification
= notification_name
;
91 void SecKeychainChanged() {
92 static dispatch_once_t once
;
93 static sec_action_t action
;
95 dispatch_once(&once
, ^{
96 action
= sec_action_create("SecKeychainChanged", 1);
97 sec_action_set_handler(action
, ^{
98 uint32_t result
= notify_post(g_keychain_changed_notification
);
99 if (result
== NOTIFY_STATUS_OK
)
100 secnotice("item", "Sent %s", g_keychain_changed_notification
);
102 secerror("notify_post %s returned: %" PRIu32
, g_keychain_changed_notification
, result
);
106 sec_action_perform(action
);
109 /* Return the current database version in *version. */
110 static bool SecKeychainDbGetVersion(SecDbConnectionRef dbt
, int *version
, CFErrorRef
*error
)
112 __block
bool ok
= true;
113 __block CFErrorRef localError
= NULL
;
114 __block
bool found
= false;
117 * First check for the version table itself
120 ok
&= SecDbPrepare(dbt
, CFSTR("SELECT name FROM sqlite_master WHERE type='table' AND name='tversion'"), &localError
, ^(sqlite3_stmt
*stmt
) {
121 ok
= SecDbStep(dbt
, stmt
, NULL
, ^(bool *stop
) {
126 require_action(ok
, out
, SecDbError(SQLITE_CORRUPT
, error
, CFSTR("Failed to read sqlite_master table: %@"), localError
));
128 secnotice("upgr", "no tversion table, will setup a new database: %@", localError
);
134 * Now build up major.minor
137 ok
&= SecDbPrepare(dbt
, CFSTR("SELECT version FROM tversion"), &localError
, ^(sqlite3_stmt
*stmt
) {
138 ok
= SecDbStep(dbt
, stmt
, NULL
, ^(bool *stop
) {
139 *version
= sqlite3_column_int(stmt
, 0);
144 if (ok
&& (*version
& 0xffff) >= 9) {
145 ok
&= SecDbPrepare(dbt
, CFSTR("SELECT minor FROM tversion WHERE version = ?"), &localError
, ^(sqlite3_stmt
*stmt
) {
146 ok
= SecDbBindInt(stmt
, 1, *version
, &localError
) &&
147 SecDbStep(dbt
, stmt
, NULL
, ^(bool *stop
) {
148 int64_t minor
= sqlite3_column_int64(stmt
, 0);
149 *version
|= ((minor
& 0xff) << 8) | ((minor
& 0xff0000) << 8);
156 secnotice("upgr", "database version is: 0x%08x : %d : %@", *version
, ok
, localError
);
157 CFReleaseSafe(localError
);
164 isClassD(SecDbItemRef item
)
166 CFTypeRef accessible
= SecDbItemGetCachedValueWithName(item
, kSecAttrAccessible
);
168 if (CFEqualSafe(accessible
, kSecAttrAccessibleAlways
) || CFEqualSafe(accessible
, kSecAttrAccessibleAlwaysThisDeviceOnly
))
173 #if TARGET_OS_EMBEDDED
176 measureDuration(struct timeval
*start
)
181 gettimeofday(&stop
, NULL
);
183 duration
= (stop
.tv_sec
-start
->tv_sec
) * 1000;
184 duration
+= (stop
.tv_usec
/ 1000) - (start
->tv_usec
/ 1000);
186 return SecBucket2Significant(duration
);
190 measureUpgradePhase1(struct timeval
*start
, bool success
, int64_t itemsMigrated
)
192 int64_t duration
= measureDuration(start
);
195 SecADSetValueForScalarKey(CFSTR("com.apple.keychain.phase1.migrated-items-success"), itemsMigrated
);
196 SecADSetValueForScalarKey(CFSTR("com.apple.keychain.phase1.migrated-time-success"), duration
);
198 SecADSetValueForScalarKey(CFSTR("com.apple.keychain.phase1.migrated-items-fail"), itemsMigrated
);
199 SecADSetValueForScalarKey(CFSTR("com.apple.keychain.phase1.migrated-time-fail"), duration
);
204 measureUpgradePhase2(struct timeval
*start
, int64_t itemsMigrated
)
206 int64_t duration
= measureDuration(start
);
208 SecADSetValueForScalarKey(CFSTR("com.apple.keychain.phase2.migrated-items"), itemsMigrated
);
209 SecADSetValueForScalarKey(CFSTR("com.apple.keychain.phase2.migrated-time"), duration
);
211 #endif /* TARGET_OS_EMBEDDED */
213 static bool DBClassesAreEqual(const SecDbClass
* class1
, const SecDbClass
* class2
)
215 if (CFEqual(class1
->name
, class2
->name
) && class1
->itemclass
== class2
->itemclass
) {
217 const SecDbAttr
* class1Attr
= class1
->attrs
[attrIndex
];
218 const SecDbAttr
* class2Attr
= class2
->attrs
[attrIndex
];
220 while (class1Attr
&& class2Attr
) {
221 if (CFEqual(class1Attr
->name
, class2Attr
->name
) && class1Attr
->kind
== class2Attr
->kind
&& class1Attr
->flags
== class2Attr
->flags
&& class1Attr
->copyValue
== class2Attr
->copyValue
&& class1Attr
->setValue
== class2Attr
->setValue
) {
223 class1Attr
= class1
->attrs
[attrIndex
];
224 class2Attr
= class2
->attrs
[attrIndex
];
231 // if everything has checked out to this point, and we've hit the end of both class's attr list, then they're equal
232 if (class1Attr
== NULL
&& class2Attr
== NULL
) {
240 static bool ShouldRenameTable(const SecDbClass
* class1
, const SecDbClass
* class2
, int oldTableVersion
)
242 return oldTableVersion
< 10 || !DBClassesAreEqual(class1
, class2
);
245 // Goes through all tables represented by old_schema and tries to migrate all items from them into new (current version) tables.
246 static bool UpgradeSchemaPhase1(SecDbConnectionRef dbt
, const SecDbSchema
*oldSchema
, CFErrorRef
*error
)
248 __block
bool ok
= true;
249 const SecDbSchema
*newSchema
= current_schema();
250 SecDbClass
const *const *oldClass
;
251 SecDbClass
const *const *newClass
;
252 SecDbQueryRef query
= NULL
;
253 CFMutableStringRef sql
= NULL
;
254 SecDbClass
* renamedOldClass
= NULL
;
255 #if TARGET_OS_EMBEDDED
256 __block
int64_t itemsMigrated
= 0;
257 struct timeval start
;
259 gettimeofday(&start
, NULL
);
262 // Rename existing tables to names derived from old schema names
263 sql
= CFStringCreateMutable(NULL
, 0);
264 bool oldClassDone
= false;
265 CFMutableArrayRef classIndexesForNewTables
= CFArrayCreateMutable(NULL
, 0, NULL
);
267 for (oldClass
= oldSchema
->classes
, newClass
= newSchema
->classes
;
268 *newClass
!= NULL
; classIndex
++, oldClass
++, newClass
++) {
269 oldClassDone
|= (*oldClass
) == NULL
; // Check if the new schema has more tables than the old
271 if (!oldClassDone
&& !CFEqual((*oldClass
)->name
, (*newClass
)->name
) && ShouldRenameTable(*oldClass
, *newClass
, oldSchema
->majorVersion
)) {
272 CFStringAppendFormat(sql
, NULL
, CFSTR("ALTER TABLE %@ RENAME TO %@_old;"), (*newClass
)->name
, (*oldClass
)->name
);
273 CFArrayAppendValue(classIndexesForNewTables
, (void*)(long)classIndex
);
275 } else if (!oldClassDone
&& !DBClassesAreEqual(*oldClass
, *newClass
)) {
276 CFStringAppendFormat(sql
, NULL
, CFSTR("ALTER TABLE %@ RENAME TO %@_old;"), (*newClass
)->name
, (*oldClass
)->name
);
277 CFArrayAppendValue(classIndexesForNewTables
, (void*)(long)classIndex
);
280 if(oldClassDone
&& *newClass
) {
281 // These should be no-ops, unless you're upgrading a previously-upgraded database with an invalid version number
282 CFStringAppendFormat(sql
, NULL
, CFSTR("DROP TABLE IF EXISTS %@;"), (*newClass
)->name
);
284 if (classIndexesForNewTables
) {
285 CFArrayAppendValue(classIndexesForNewTables
, (void*)(long)classIndex
);
290 if(CFStringGetLength(sql
) > 0) {
291 require_quiet(ok
&= SecDbExec(dbt
, sql
, error
), out
);
295 // Drop indices that that new schemas will use
296 sql
= CFStringCreateMutable(NULL
, 0);
297 for (newClass
= newSchema
->classes
; *newClass
!= NULL
; newClass
++) {
298 SecDbForEachAttrWithMask((*newClass
),desc
, kSecDbIndexFlag
| kSecDbInFlag
) {
299 CFStringAppendFormat(sql
, 0, CFSTR("DROP INDEX IF EXISTS %@%@;"), (*newClass
)->name
, desc
->name
);
302 require_quiet(ok
&= SecDbExec(dbt
, sql
, error
), out
);
305 // Create tables for new schema.
306 require_quiet(ok
&= SecItemDbCreateSchema(dbt
, newSchema
, classIndexesForNewTables
, false, error
), out
);
307 // Go through all classes of current schema to transfer all items to new tables.
308 for (oldClass
= oldSchema
->classes
, newClass
= newSchema
->classes
;
309 *oldClass
!= NULL
&& *newClass
!= NULL
; oldClass
++, newClass
++) {
311 if (CFEqual((*oldClass
)->name
, (*newClass
)->name
) && DBClassesAreEqual(*oldClass
, *newClass
)) {
315 secnotice("upgr", "Upgrading table %@", (*oldClass
)->name
);
317 // Create a new 'old' class with a new 'old' name.
319 SecDbForEachAttr(*oldClass
, attr
) {
322 if(renamedOldClass
) {
323 CFReleaseNull(renamedOldClass
->name
);
324 free(renamedOldClass
);
326 renamedOldClass
= (SecDbClass
*) malloc(sizeof(SecDbClass
) + sizeof(SecDbAttr
*)*(count
+1));
327 renamedOldClass
->name
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("%@_old"), (*oldClass
)->name
);
328 renamedOldClass
->itemclass
= (*oldClass
)->itemclass
;
329 for(; count
>= 0; count
--) {
330 renamedOldClass
->attrs
[count
] = (*oldClass
)->attrs
[count
];
333 // SecDbItemSelect only works for item classes.
334 if((*oldClass
)->itemclass
) {
335 // Prepare query to iterate through all items in cur_class.
337 query_destroy(query
, NULL
);
338 require_quiet(query
= query_create(renamedOldClass
, SecMUSRGetAllViews(), NULL
, error
), out
);
340 ok
&= SecDbItemSelect(query
, dbt
, error
, ^bool(const SecDbAttr
*attr
) {
341 // We are interested in all attributes which are physically present in the DB.
342 return (attr
->flags
& kSecDbInFlag
) != 0;
343 }, ^bool(const SecDbAttr
*attr
) {
344 // No filtering please.
346 }, NULL
, NULL
, ^(SecDbItemRef item
, bool *stop
) {
347 CFErrorRef localError
= NULL
;
349 #if TARGET_OS_EMBEDDED
352 // Switch item to the new class.
353 item
->class = *newClass
;
355 if (isClassD(item
)) {
357 ok
&= SecDbItemEnsureDecrypted(item
, &localError
);
358 require_quiet(ok
, out
);
360 // Delete SHA1 field from the item, so that it is newly recalculated before storing
361 // the item into the new table.
362 require_quiet(ok
&= SecDbItemSetValue(item
, SecDbClassAttrWithKind(item
->class, kSecDbSHA1Attr
, error
),
363 kCFNull
, error
), out
);
365 // Leave item encrypted, do not ever try to decrypt it since it will fail.
366 item
->_edataState
= kSecDbItemAlwaysEncrypted
;
368 // Drop items with kSecAttrAccessGroupToken, as these items should not be there at all. Since agrp attribute
369 // is always stored as cleartext in the DB column, we can always rely on this attribute being present in item->attributes.
370 // <rdar://problem/33401870>
371 if (CFEqualSafe(SecDbItemGetCachedValueWithName(item
, kSecAttrAccessGroup
), kSecAttrAccessGroupToken
) &&
372 SecDbItemGetCachedValueWithName(item
, kSecAttrTokenID
) == NULL
) {
373 secnotice("upgr", "dropping item during schema upgrade due to agrp=com.apple.token: %@", item
);
375 // Insert new item into the new table.
376 if (!SecDbItemInsert(item
, dbt
, &localError
)) {
377 secerror("item: %@ insert during upgrade: %@", item
, localError
);
384 OSStatus status
= SecErrorGetOSStatus(localError
);
387 // continue to upgrade and don't propagate errors for insert failures
388 // that are typical of a single item failure
390 case errSecDuplicateItem
:
393 case errSecInteractionNotAllowed
:
394 case errSecAuthNeeded
:
397 // This does not mean the keychain is hosed, we just can't use it right now
399 case kAKSReturnNotReady
:
400 case kAKSReturnTimeout
:
402 case errSecNotAvailable
:
403 secnotice("upgr", "Bailing in phase 1 because AKS is unavailable: %@", localError
);
406 ok
&= CFErrorPropagate(CFRetainSafe(localError
), error
);
409 CFReleaseSafe(localError
);
415 require_quiet(ok
, out
);
417 // This table does not contain secdb items, and must be transferred without using SecDbItemSelect.
418 // For now, this code does not support removing or renaming any columns, or adding any new non-null columns.
420 sql
= CFStringCreateMutable(NULL
, 0);
423 CFMutableStringRef columns
= CFStringCreateMutable(NULL
, 0);
425 SecDbForEachAttr(renamedOldClass
, attr
) {
427 CFStringAppendFormat(columns
, NULL
, CFSTR(","));
429 CFStringAppendFormat(columns
, NULL
, CFSTR("%@"), attr
->name
);
433 CFStringAppendFormat(sql
, NULL
, CFSTR("INSERT INTO %@ (%@) SELECT %@ FROM %@;"), (*newClass
)->name
, columns
, columns
, renamedOldClass
->name
);
435 CFReleaseNull(columns
);
436 require_quiet(ok
&= SecDbExec(dbt
, sql
, error
), out
);
440 // Remove old tables from the DB.
442 sql
= CFStringCreateMutable(NULL
, 0);
443 for (oldClass
= oldSchema
->classes
, newClass
= newSchema
->classes
;
444 *oldClass
!= NULL
&& *newClass
!= NULL
; oldClass
++, newClass
++) {
445 if (CFEqual((*oldClass
)->name
, (*newClass
)->name
) && DBClassesAreEqual(*oldClass
, *newClass
)) {
449 CFStringAppendFormat(sql
, NULL
, CFSTR("DROP TABLE %@_old;"), (*oldClass
)->name
);
452 if(CFStringGetLength(sql
) > 0) {
453 require_quiet(ok
&= SecDbExec(dbt
, sql
, error
), out
);
456 #if TARGET_OS_EMBEDDED
457 measureUpgradePhase1(&start
, ok
, SecBucket2Significant(itemsMigrated
));
461 query_destroy(query
, NULL
);
464 CFReleaseNull(classIndexesForNewTables
);
465 if(renamedOldClass
) {
466 CFReleaseNull(renamedOldClass
->name
);
467 free(renamedOldClass
);
472 // Goes through all tables represented by old_schema and tries to migrate all items from them into new (current version) tables.
473 static bool UpgradeItemPhase2(SecDbConnectionRef dbt
, bool *inProgress
, CFErrorRef
*error
) {
474 __block
bool ok
= true;
475 SecDbQueryRef query
= NULL
;
476 #if TARGET_OS_EMBEDDED
477 __block
int64_t itemsMigrated
= 0;
478 struct timeval start
;
480 gettimeofday(&start
, NULL
);
483 // Go through all classes in new schema
484 const SecDbSchema
*newSchema
= current_schema();
485 for (const SecDbClass
*const *class = newSchema
->classes
; *class != NULL
&& !*inProgress
; class++) {
486 if(!((*class)->itemclass
)) {
487 //Don't try to decrypt non-item 'classes'
491 const SecDbAttr
*pdmn
= SecDbClassAttrWithKind(*class, kSecDbAccessAttr
, error
);
496 // Prepare query to go through all non-DK|DKU items
498 query_destroy(query
, NULL
);
500 require_action_quiet(query
= query_create(*class, SecMUSRGetAllViews(), NULL
, error
), out
, ok
= false);
501 ok
= SecDbItemSelect(query
, dbt
, error
, NULL
, ^bool(const SecDbAttr
*attr
) {
502 // No simple per-attribute filtering.
504 }, ^bool(CFMutableStringRef sql
, bool *needWhere
) {
505 // Select only non-D-class items
506 SecDbAppendWhereOrAnd(sql
, needWhere
);
507 CFStringAppendFormat(sql
, NULL
, CFSTR("NOT %@ IN (?,?)"), pdmn
->name
);
509 }, ^bool(sqlite3_stmt
*stmt
, int col
) {
510 return SecDbBindObject(stmt
, col
++, kSecAttrAccessibleAlwaysPrivate
, error
) &&
511 SecDbBindObject(stmt
, col
++, kSecAttrAccessibleAlwaysThisDeviceOnlyPrivate
, error
);
512 }, ^(SecDbItemRef item
, bool *stop
) {
513 CFErrorRef localError
= NULL
;
515 #if TARGET_OS_EMBEDDED
520 if (SecDbItemEnsureDecrypted(item
, &localError
)) {
522 // Delete SHA1 field from the item, so that it is newly recalculated before storing
523 // the item into the new table.
524 require_quiet(ok
= SecDbItemSetValue(item
, SecDbClassAttrWithKind(item
->class, kSecDbSHA1Attr
, error
),
525 kCFNull
, &localError
), out
);
526 // Drop items with kSecAttrAccessGroupToken, as these items should not be there at all. Since agrp attribute
527 // is always stored as cleartext in the DB column, we can always rely on this attribute being present in item->attributes.
528 // <rdar://problem/33401870>
529 if (CFEqualSafe(SecDbItemGetCachedValueWithName(item
, kSecAttrAccessGroup
), kSecAttrAccessGroupToken
) &&
530 SecDbItemGetCachedValueWithName(item
, kSecAttrTokenID
) == NULL
) {
531 secnotice("upgr", "dropping item during item upgrade due to agrp=com.apple.token: %@", item
);
532 ok
= SecDbItemDelete(item
, dbt
, kCFBooleanFalse
, &localError
);
534 // Replace item with the new value in the table; this will cause the item to be decoded and recoded back,
535 // incl. recalculation of item's hash.
536 ok
= SecDbItemUpdate(item
, item
, dbt
, false, query
->q_uuid_from_primary_key
, &localError
);
541 CFIndex status
= CFErrorGetCode(localError
);
545 // Items producing errSecDecode are silently dropped - they are not decodable and lost forever.
546 (void)SecDbItemDelete(item
, dbt
, false, error
);
549 case errSecInteractionNotAllowed
:
550 // If we are still not able to decrypt the item because the class key is not released yet,
551 // remember that DB still needs phase2 migration to be run next time a connection is made. Also
552 // stop iterating next items, it would be just waste of time because the whole iteration will be run
553 // next time when this phase2 will be rerun.
558 case errSecAuthNeeded
:
559 // errSecAuthNeeded means that it is an ACL-based item which requires authentication (or at least
560 // ACM context, which we do not have).
564 case kAKSReturnNotReady
:
565 case kAKSReturnTimeout
:
567 case errSecNotAvailable
:
568 secnotice("upgr", "Bailing in phase 2 because AKS is unavailable: %@", localError
);
571 // Other errors should abort the migration completely.
572 ok
= CFErrorPropagate(CFRetainSafe(localError
), error
);
578 CFReleaseSafe(localError
);
579 *stop
= *stop
|| !ok
;
585 #if TARGET_OS_EMBEDDED
586 measureUpgradePhase2(&start
, SecBucket2Significant(itemsMigrated
));
591 query_destroy(query
, NULL
);
595 #define SCHEMA_VERSION(schema) ((((schema)->minorVersion) << 8) | ((schema)->majorVersion))
596 #define VERSION_MAJOR(version) ((version) & 0xff)
597 #define VERSION_MINOR(version) (((version) >> 8) & 0xff)
598 #define VERSION_NEW(version) ((version) & 0xffff)
599 #define VERSION_OLD(version) (((version) >> 16) & 0xffff)
601 static bool SecKeychainDbUpgradeFromVersion(SecDbConnectionRef dbt
, int version
, bool *inProgress
, CFErrorRef
*error
) {
602 __block
bool didPhase2
= false;
603 __block
bool ok
= true;
604 __block CFErrorRef localError
= NULL
;
609 const SecDbSchema
*newSchema
= current_schema();
611 // If DB schema is the one we want, we are done.
612 require_quiet(SCHEMA_VERSION(newSchema
) != version
, out
);
614 // Check if the schema of the database on disk is the same major, but newer version then what we have
615 // in code, lets just skip this since a newer version of the OS have upgrade it. Since its the same
616 // major, its a promise that it will be compatible.
617 if (newSchema
->majorVersion
== VERSION_MAJOR(version
) && newSchema
->minorVersion
< VERSION_MINOR(version
)) {
618 secnotice("upgr", "skipping upgrade since minor is newer");
622 if (VERSION_MAJOR(version
) < 6) {
623 // Pre v6 keychains need to have WAL enabled, since SecDb only does this at db creation time.
624 // NOTE: This has to be run outside of a transaction.
625 require_action_quiet(ok
= (SecDbExec(dbt
, CFSTR("PRAGMA auto_vacuum = FULL"), &localError
) &&
626 SecDbExec(dbt
, CFSTR("PRAGMA journal_mode = WAL"), &localError
)),
627 out
, secerror("upgrade: unable to enable WAL or auto vacuum, marking DB as corrupt: %@",
631 ok
&= SecDbTransaction(dbt
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
632 CFStringRef sql
= NULL
;
633 bool didPhase1
= false;
635 // Get version again once we start a transaction, someone else might change the migration state.
637 require_quiet(ok
= SecKeychainDbGetVersion(dbt
, &version2
, &localError
), out
);
638 // Check if someone has raced us to the migration of the database
639 require_action(version
== version2
, out
, CFReleaseNull(localError
); ok
= true);
641 require_quiet(SCHEMA_VERSION(newSchema
) != version2
, out
);
643 // If this is empty database, just create table according to schema and be done with it.
644 require_action_quiet(version2
!= 0, out
, ok
= SecItemDbCreateSchema(dbt
, newSchema
, NULL
, true, &localError
));
646 int oldVersion
= VERSION_OLD(version2
);
647 version2
= VERSION_NEW(version2
);
649 require_action_quiet(version2
== SCHEMA_VERSION(newSchema
) || oldVersion
== 0, out
,
650 ok
= SecDbError(SQLITE_CORRUPT
, &localError
,
651 CFSTR("Half migrated but obsolete DB found: found 0x%x(0x%x) but 0x%x is needed"),
652 version2
, oldVersion
, SCHEMA_VERSION(newSchema
)));
654 // Check whether we have both old and new tables in the DB.
655 if (oldVersion
== 0) {
656 // Pure old-schema migration attempt, with full blown table renames etc (a.k.a. phase1)
657 oldVersion
= version2
;
658 version2
= SCHEMA_VERSION(newSchema
);
660 // Find schema for old database.
661 const SecDbSchema
*oldSchema
= NULL
;
662 for (const SecDbSchema
* const *pschema
= all_schemas(); *pschema
; ++pschema
) {
663 if (SCHEMA_VERSION((*pschema
)) == oldVersion
) {
664 oldSchema
= *pschema
;
669 // If we are attempting to upgrade from a version for which we have no schema, fail.
670 require_action_quiet(oldSchema
!= NULL
, out
,
671 ok
= SecDbError(SQLITE_CORRUPT
, &localError
, CFSTR("no schema for version: 0x%x"), oldVersion
);
672 secerror("no schema for version 0x%x", oldVersion
));
674 secnotice("upgr", "Upgrading from version 0x%x to 0x%x", oldVersion
, SCHEMA_VERSION(newSchema
));
675 require_action(ok
= UpgradeSchemaPhase1(dbt
, oldSchema
, &localError
), out
, secerror("upgrade: Upgrade phase1 failed: %@", localError
));
681 CFErrorRef phase2Error
= NULL
;
683 // Lests try to go through non-D-class items in new tables and apply decode/encode on them
684 // If this fails the error will be ignored after doing a phase1 since but not in the second
685 // time when we are doing phase2.
686 ok
= UpgradeItemPhase2(dbt
, inProgress
, &phase2Error
);
692 SecErrorPropagate(phase2Error
, &localError
);
695 CFReleaseNull(phase2Error
);
696 require_action(ok
, out
, secerror("upgrade: Upgrade phase2 (%d) failed: %@", didPhase1
, localError
));
699 // If either migration path we did reported that the migration was complete, signalize that
700 // in the version database by cleaning oldVersion (which is stored in upper halfword of the version)
701 secnotice("upgr", "Done upgrading from version 0x%x to 0x%x", oldVersion
, SCHEMA_VERSION(newSchema
));
708 // Update database version table.
709 uint32_t major
= (VERSION_MAJOR(version2
)) | (VERSION_MAJOR(oldVersion
) << 16);
710 uint32_t minor
= (VERSION_MINOR(version2
)) | (VERSION_MINOR(oldVersion
) << 16);
711 secnotice("upgr", "Upgrading saving version major 0x%x minor 0x%x", major
, minor
);
712 sql
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("UPDATE tversion SET version='%d', minor='%d'"),
714 require_action_quiet(ok
= SecDbExec(dbt
, sql
, &localError
), out
, secerror("upgrade: Setting version failed: %@", localError
));
718 secerror("upgrade: SecDB upgrade failed: %@", localError
);
724 if (ok
&& didPhase2
) {
725 #if TARGET_OS_EMBEDDED
726 SecADSetValueForScalarKey(CFSTR("com.apple.keychain.migration-success"), 1);
731 if (!ok
|| localError
) {
732 // TODO: This logic should be inverted to a do-not-corrupt-unless default, <rdar://problem/29771874>
734 * We assume that database is corrupt at this point, but we need to
735 * check if the error we got isn't severe enough to mark the database as corrupt.
736 * In those cases we opt out of corrupting the database.
738 bool markedCorrupt
= true;
741 secwarning("upgrade: error has been set but status is true");
744 secerror("upgrade: error occurred, considering marking database as corrupt: %@", localError
);
746 CFStringRef domain
= CFErrorGetDomain(localError
);
747 CFIndex code
= CFErrorGetCode(localError
);
749 if ((CFEqualSafe(domain
, kSecDbErrorDomain
) &&
750 ((code
& 0xff) == SQLITE_LOCKED
|| (code
& 0xff) == SQLITE_BUSY
|| (code
& 0xff) == SQLITE_FULL
)) ||
752 code
== kAKSReturnNotReady
|| code
== kAKSReturnTimeout
||
754 code
== errSecNotAvailable
)
756 secerror("upgrade: not marking keychain database corrupt for error: %@", localError
);
757 markedCorrupt
= false;
758 CFReleaseNull(localError
);
760 secerror("upgrade: unable to complete upgrade, marking DB as corrupt: %@", localError
);
763 secerror("upgrade: unable to complete upgrade and no error object returned, marking DB as corrupt");
766 secerror("upgrade: marking database as corrupt");
767 SecDbCorrupt(dbt
, localError
);
768 #if TARGET_OS_EMBEDDED
769 SecADSetValueForScalarKey(CFSTR("com.apple.keychain.migration-failure"), 1);
775 *error
= (CFErrorRef
)CFRetain(localError
);
777 CFReleaseNull(localError
);
783 static bool accessGroupIsNetworkExtensionAndClientIsEntitled(CFStringRef accessGroup
, SecurityClient
* client
)
785 return client
&& client
->canAccessNetworkExtensionAccessGroups
&& accessGroup
&& CFStringHasSuffix(accessGroup
, kSecNetworkExtensionAccessGroupSuffix
);
788 /* AUDIT[securityd](done):
789 accessGroup (ok) is a caller provided, non NULL CFTypeRef.
791 Return true iff accessGroup is allowable according to accessGroups.
793 bool accessGroupsAllows(CFArrayRef accessGroups
, CFStringRef accessGroup
, SecurityClient
* client
) {
794 /* NULL accessGroups is wildcard. */
797 /* Make sure we have a string. */
798 if (!isString(accessGroup
))
801 /* Having the special accessGroup "*" allows access to all accessGroups. */
802 CFRange range
= { 0, CFArrayGetCount(accessGroups
) };
804 (CFArrayContainsValue(accessGroups
, range
, accessGroup
) ||
805 CFArrayContainsValue(accessGroups
, range
, CFSTR("*")) ||
806 accessGroupIsNetworkExtensionAndClientIsEntitled(accessGroup
, client
)))
812 bool itemInAccessGroup(CFDictionaryRef item
, CFArrayRef accessGroups
) {
813 return accessGroupsAllows(accessGroups
,
814 CFDictionaryGetValue(item
, kSecAttrAccessGroup
), NULL
);
818 static CF_RETURNS_RETAINED CFDataRef
SecServerExportBackupableKeychain(SecDbConnectionRef dbt
,
819 SecurityClient
*client
,
820 keybag_handle_t src_keybag
, keybag_handle_t dest_keybag
, CFErrorRef
*error
) {
821 CFDataRef data_out
= NULL
;
822 /* Export everything except the items for which SecItemIsSystemBound()
824 CFDictionaryRef keychain
= SecServerCopyKeychainPlist(dbt
, client
,
825 src_keybag
, dest_keybag
, kSecBackupableItemFilter
,
828 data_out
= CFPropertyListCreateData(kCFAllocatorDefault
, keychain
,
829 kCFPropertyListBinaryFormat_v1_0
,
837 static bool SecServerImportBackupableKeychain(SecDbConnectionRef dbt
,
838 SecurityClient
*client
,
839 keybag_handle_t src_keybag
,
840 keybag_handle_t dest_keybag
,
844 return kc_transaction(dbt
, error
, ^{
846 CFDictionaryRef keychain
;
847 keychain
= CFPropertyListCreateWithData(kCFAllocatorDefault
, data
,
848 kCFPropertyListImmutable
, NULL
,
851 if (isDictionary(keychain
)) {
852 ok
= SecServerImportKeychainInPlist(dbt
,
857 kSecBackupableItemFilter
,
860 ok
= SecError(errSecParam
, error
, CFSTR("import: keychain is not a dictionary"));
870 * Similar to ks_open_keybag, but goes through MKB interface
872 static bool mkb_open_keybag(CFDataRef keybag
, CFDataRef password
, MKBKeyBagHandleRef
*handle
, CFErrorRef
*error
) {
874 MKBKeyBagHandleRef mkbhandle
= NULL
;
876 rc
= MKBKeyBagCreateWithData(keybag
, &mkbhandle
);
877 if (rc
!= kMobileKeyBagSuccess
) {
878 return SecKernError(rc
, error
, CFSTR("MKBKeyBagCreateWithData failed: %d"), rc
);
882 rc
= MKBKeyBagUnlock(mkbhandle
, password
);
883 if (rc
!= kMobileKeyBagSuccess
) {
884 CFRelease(mkbhandle
);
885 return SecKernError(rc
, error
, CFSTR("failed to unlock bag: %d"), rc
);
896 static CFDataRef
SecServerKeychainCreateBackup(SecDbConnectionRef dbt
, SecurityClient
*client
, CFDataRef keybag
,
897 CFDataRef password
, CFErrorRef
*error
) {
898 CFDataRef backup
= NULL
;
899 keybag_handle_t backup_keybag
;
901 MKBKeyBagHandleRef mkbhandle
= NULL
;
902 require(mkb_open_keybag(keybag
, password
, &mkbhandle
, error
), out
);
904 require_noerr(MKBKeyBagGetAKSHandle(mkbhandle
, &backup_keybag
), out
);
907 backup_keybag
= KEYBAG_NONE
;
909 /* Export from system keybag to backup keybag. */
910 backup
= SecServerExportBackupableKeychain(dbt
, client
, KEYBAG_DEVICE
, backup_keybag
, error
);
915 CFRelease(mkbhandle
);
920 static bool SecServerKeychainRestore(SecDbConnectionRef dbt
,
921 SecurityClient
*client
,
928 keybag_handle_t backup_keybag
;
930 MKBKeyBagHandleRef mkbhandle
= NULL
;
931 require(mkb_open_keybag(keybag
, password
, &mkbhandle
, error
), out
);
933 require_noerr(MKBKeyBagGetAKSHandle(mkbhandle
, &backup_keybag
), out
);
935 backup_keybag
= KEYBAG_NONE
;
937 /* Import from backup keybag to system keybag. */
938 require(SecServerImportBackupableKeychain(dbt
, client
, backup_keybag
, KEYBAG_DEVICE
, backup
, error
), out
);
944 CFRelease(mkbhandle
);
948 secwarning("Restore completed sucessfully");
950 secwarning("Restore failed with: %@", error
? *error
: NULL
);
957 // MARK - External SPI support code.
959 CFStringRef
__SecKeychainCopyPath(void) {
960 CFStringRef kcRelPath
= NULL
;
962 kcRelPath
= CFSTR("keychain-2.db");
964 kcRelPath
= CFSTR("keychain-2-debug.db");
967 CFStringRef kcPath
= NULL
;
968 CFURLRef kcURL
= SecCopyURLForFileInKeychainDirectory(kcRelPath
);
970 kcPath
= CFURLCopyFileSystemPath(kcURL
, kCFURLPOSIXPathStyle
);
977 // MARK: kc_dbhandle init and reset
979 SecDbRef
SecKeychainDbCreate(CFStringRef path
, CFErrorRef
* error
) {
980 __block CFErrorRef localerror
= NULL
;
982 SecDbRef kc
= SecDbCreate(path
, ^bool (SecDbRef db
, SecDbConnectionRef dbconn
, bool didCreate
, bool *callMeAgainForNextConnection
, CFErrorRef
*error
) {
983 // Upgrade from version 0 means create the schema in empty db.
987 ok
= SecKeychainDbGetVersion(dbconn
, &version
, error
);
989 ok
= ok
&& SecKeychainDbUpgradeFromVersion(dbconn
, version
, callMeAgainForNextConnection
, error
);
991 secerror("Upgrade %sfailed: %@", didCreate
? "from v0 " : "", error
? *error
: NULL
);
993 localerror
= error
? *error
: NULL
;
996 // This block might get called many, many times due to callMeAgainForNextConnection.
997 // When we no longer want to be called, we believe we're done. Begin the rest of initialization.
998 if( !callMeAgainForNextConnection
|| !(*callMeAgainForNextConnection
)) {
999 SecKeychainDbInitialize(db
);
1007 *error
= localerror
;
1013 SecDbRef
SecKeychainDbInitialize(SecDbRef db
) {
1016 if(SecCKKSIsEnabled()) {
1017 // This needs to be async, otherwise we get hangs between securityd, cloudd, and apsd
1018 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT
, 0), ^{
1019 SecCKKSInitialize(db
);
1028 static SecDbRef _kc_dbhandle
= NULL
;
1029 static dispatch_queue_t _kc_dbhandle_dispatch
= NULL
;
1030 static dispatch_once_t _kc_dbhandle_dispatch_onceToken
= 0;
1031 static dispatch_queue_t
get_kc_dbhandle_dispatch() {
1032 dispatch_once(&_kc_dbhandle_dispatch_onceToken
, ^{
1033 _kc_dbhandle_dispatch
= dispatch_queue_create("sec_kc_dbhandle", DISPATCH_QUEUE_SERIAL
);
1036 return _kc_dbhandle_dispatch
;
1039 static bool kc_dbhandle_init(CFErrorRef
* error
) {
1040 SecDbRef oldHandle
= _kc_dbhandle
;
1041 _kc_dbhandle
= NULL
;
1042 CFStringRef dbPath
= __SecKeychainCopyPath();
1044 _kc_dbhandle
= SecKeychainDbCreate(dbPath
, error
);
1047 secerror("no keychain path available");
1050 secerror("replaced %@ with %@", oldHandle
, _kc_dbhandle
);
1051 CFRelease(oldHandle
);
1053 // Having a dbhandle means we succeeded.
1054 return !!_kc_dbhandle
;
1057 static SecDbRef
kc_dbhandle(CFErrorRef
* error
)
1059 dispatch_sync(get_kc_dbhandle_dispatch(), ^{
1060 if(_kc_dbhandle
== NULL
) {
1061 _SecDbServerSetup();
1062 kc_dbhandle_init(error
);
1065 return _kc_dbhandle
;
1068 /* For whitebox testing only */
1069 void SecKeychainDbReset(dispatch_block_t inbetween
)
1071 dispatch_sync(get_kc_dbhandle_dispatch(), ^{
1072 CFStringRef dbPath
= __SecKeychainCopyPath();
1076 CFReleaseNull(_kc_dbhandle
);
1081 CFErrorRef error
= NULL
;
1082 _kc_dbhandle
= SecKeychainDbCreate(dbPath
, &error
);
1085 secerror("error resetting database: %@", error
);
1092 static SecDbConnectionRef
kc_acquire_dbt(bool writeAndRead
, CFErrorRef
*error
) {
1093 SecDbRef db
= kc_dbhandle(error
);
1095 if(error
&& !(*error
)) {
1096 SecError(errSecDataNotAvailable
, error
, CFSTR("failed to get a db handle"));
1100 return SecDbConnectionAcquire(db
, !writeAndRead
, error
);
1103 /* Return a per thread dbt handle for the keychain. If create is true create
1104 the database if it does not yet exist. If it is false, just return an
1105 error if it fails to auto-create. */
1106 __thread SecDbConnectionRef dbt
= NULL
;
1107 bool kc_with_dbt(bool writeAndRead
, CFErrorRef
*error
, bool (^perform
)(SecDbConnectionRef dbt
))
1110 // The kc_with_dbt upthread will clean this up when it's done.
1111 return perform(dbt
);
1113 // Make sure we initialize our engines before writing to the keychain
1115 SecItemDataSourceFactoryGetDefault();
1118 dbt
= kc_acquire_dbt(writeAndRead
, error
);
1121 SecDbConnectionRelease(dbt
);
1128 items_matching_issuer_parent(SecDbConnectionRef dbt
, CFArrayRef accessGroups
, CFDataRef musrView
,
1129 CFDataRef issuer
, CFArrayRef issuers
, int recurse
)
1132 CFArrayRef results
= NULL
;
1136 if (CFArrayContainsValue(issuers
, CFRangeMake(0, CFArrayGetCount(issuers
)), issuer
))
1139 /* XXX make musr supported */
1140 const void *keys
[] = { kSecClass
, kSecReturnRef
, kSecAttrSubject
};
1141 const void *vals
[] = { kSecClassCertificate
, kCFBooleanTrue
, issuer
};
1142 CFDictionaryRef query
= CFDictionaryCreate(kCFAllocatorDefault
, keys
, vals
, array_size(keys
), NULL
, NULL
);
1147 CFErrorRef localError
= NULL
;
1148 q
= query_create_with_limit(query
, musrView
, kSecMatchUnlimited
, &localError
);
1151 s3dl_copy_matching(dbt
, q
, (CFTypeRef
*)&results
, accessGroups
, &localError
);
1152 query_destroy(q
, &localError
);
1155 secerror("items matching issuer parent: %@", localError
);
1156 CFReleaseNull(localError
);
1160 count
= CFArrayGetCount(results
);
1161 for (i
= 0; (i
< count
) && !found
; i
++) {
1162 CFDictionaryRef cert_dict
= (CFDictionaryRef
)CFArrayGetValueAtIndex(results
, i
);
1163 CFDataRef cert_issuer
= CFDictionaryGetValue(cert_dict
, kSecAttrIssuer
);
1164 if (CFEqual(cert_issuer
, issuer
))
1167 found
= items_matching_issuer_parent(dbt
, accessGroups
, musrView
, cert_issuer
, issuers
, recurse
);
1169 CFReleaseSafe(results
);
1175 _FilterWithPolicy(SecPolicyRef policy
, CFDateRef date
, SecCertificateRef cert
)
1177 CFDictionaryRef props
= NULL
;
1178 CFArrayRef keychains
= NULL
;
1179 CFArrayRef anchors
= NULL
;
1180 CFArrayRef certs
= NULL
;
1181 CFArrayRef chain
= NULL
;
1182 SecTrustRef trust
= NULL
;
1184 SecTrustResultType trustResult
;
1185 Boolean needChain
= false;
1186 __block
bool ok
= false;
1188 if (!policy
|| !cert
) return false;
1190 certs
= CFArrayCreate(NULL
, (const void **)&cert
, (CFIndex
)1, &kCFTypeArrayCallBacks
);
1191 require_noerr_quiet(SecTrustCreateWithCertificates(certs
, policy
, &trust
), cleanup
);
1193 /* Set evaluation date, if specified (otherwise current date is implied) */
1194 if (date
&& (CFGetTypeID(date
) == CFDateGetTypeID())) {
1195 require_noerr_quiet(SecTrustSetVerifyDate(trust
, date
), cleanup
);
1198 /* Check whether this is the X509 Basic policy, which means chain building */
1199 props
= SecPolicyCopyProperties(policy
);
1201 CFTypeRef oid
= (CFTypeRef
) CFDictionaryGetValue(props
, kSecPolicyOid
);
1202 if (oid
&& (CFEqual(oid
, kSecPolicyAppleX509Basic
) ||
1203 CFEqual(oid
, kSecPolicyAppleRevocation
))) {
1209 require_noerr_quiet(SecTrustEvaluateLeafOnly(trust
, &trustResult
), cleanup
);
1211 require_noerr_quiet(SecTrustEvaluate(trust
, &trustResult
), cleanup
);
1214 require_quiet((trustResult
== kSecTrustResultProceed
||
1215 trustResult
== kSecTrustResultUnspecified
||
1216 trustResult
== kSecTrustResultRecoverableTrustFailure
), cleanup
);
1219 #if TARGET_OS_IPHONE
1220 CFArrayRef properties
= SecTrustCopyProperties(trust
);
1222 CFArrayRef properties
= SecTrustCopyProperties_ios(trust
);
1225 CFArrayForEach(properties
, ^(const void *property
) {
1226 CFDictionaryForEach((CFDictionaryRef
)property
, ^(const void *key
, const void *value
) {
1227 if (CFEqual((CFTypeRef
)key
, kSecPropertyKeyType
) && CFEqual((CFTypeRef
)value
, kSecPropertyTypeError
))
1231 CFRelease(properties
);
1235 if(props
) CFRelease(props
);
1236 if(chain
) CFRelease(chain
);
1237 if(anchors
) CFRelease(anchors
);
1238 if(keychains
) CFRelease(keychains
);
1239 if(certs
) CFRelease(certs
);
1240 if(trust
) CFRelease(trust
);
1246 _FilterWithDate(CFDateRef validOnDate
, SecCertificateRef cert
)
1248 if (!validOnDate
|| !cert
) return false;
1250 CFAbsoluteTime at
, nb
, na
;
1251 at
= CFDateGetAbsoluteTime((CFDateRef
)validOnDate
);
1254 nb
= SecCertificateNotValidBefore(cert
);
1255 na
= SecCertificateNotValidAfter(cert
);
1257 if (nb
== 0 || na
== 0 || nb
== na
) {
1259 secnotice("FilterWithDate", "certificate cannot operate");
1263 secnotice("FilterWithDate", "certificate is not valid yet");
1267 secnotice("FilterWithDate", "certificate expired");
1274 _FilterWithTrust(Boolean trustedOnly
, SecCertificateRef cert
)
1276 if (!cert
) return false;
1277 if (!trustedOnly
) return true;
1280 CFArrayRef certArray
= CFArrayCreate(NULL
, (const void**)&cert
, 1, &kCFTypeArrayCallBacks
);
1281 SecTrustRef trust
= NULL
;
1282 SecPolicyRef policy
= SecPolicyCreateBasicX509();
1283 require_quiet(policy
, out
);
1285 require_noerr_quiet(SecTrustCreateWithCertificates(certArray
, policy
, &trust
), out
);
1286 SecTrustResultType trustResult
;
1287 require_noerr_quiet(SecTrustEvaluate(trust
, &trustResult
), out
);
1289 require_quiet((trustResult
== kSecTrustResultProceed
||
1290 trustResult
== kSecTrustResultUnspecified
), out
);
1293 CFReleaseSafe(trust
);
1294 CFReleaseSafe(policy
);
1295 CFReleaseSafe(certArray
);
1299 static SecCertificateRef
1300 CopyCertificateFromItem(Query
*q
, CFDictionaryRef item
) {
1301 SecCertificateRef certRef
= NULL
;
1302 CFDictionaryRef itemValue
= NULL
;
1304 CFTypeRef tokenID
= NULL
;
1305 CFDataRef certData
= NULL
;
1306 if (q
->q_class
== identity_class()) {
1307 certData
= CFDictionaryGetValue(item
, kSecAttrIdentityCertificateData
);
1308 tokenID
= CFDictionaryGetValue(item
, kSecAttrIdentityCertificateTokenID
);
1309 } else if (q
->q_class
== cert_class()) {
1310 certData
= CFDictionaryGetValue(item
, kSecValueData
);
1311 tokenID
= CFDictionaryGetValue(item
, kSecAttrTokenID
);
1314 require_quiet(certData
, out
);
1315 if (tokenID
!= NULL
) {
1316 CFErrorRef error
= NULL
;
1317 itemValue
= SecTokenItemValueCopy(certData
, &error
);
1318 require_action_quiet(itemValue
, out
, { secerror("function SecTokenItemValueCopy failed with: %@", error
); CFReleaseSafe(error
); });
1319 CFDataRef tokenCertData
= CFDictionaryGetValue(itemValue
, kSecTokenValueDataKey
);
1320 require_action_quiet(tokenCertData
, out
, { secerror("token item doesn't contain token value data");});
1321 certRef
= SecCertificateCreateWithData(kCFAllocatorDefault
, tokenCertData
);
1324 certRef
= SecCertificateCreateWithData(kCFAllocatorDefault
, certData
);
1327 CFReleaseNull(itemValue
);
1331 bool match_item(SecDbConnectionRef dbt
, Query
*q
, CFArrayRef accessGroups
, CFDictionaryRef item
)
1334 SecCertificateRef certRef
= NULL
;
1335 if (q
->q_match_issuer
) {
1336 CFDataRef issuer
= CFDictionaryGetValue(item
, kSecAttrIssuer
);
1337 if (!items_matching_issuer_parent(dbt
, accessGroups
, q
->q_musrView
, issuer
, q
->q_match_issuer
, 10 /*max depth*/))
1341 if (q
->q_match_policy
&& (q
->q_class
== identity_class() || q
->q_class
== cert_class())) {
1343 certRef
= CopyCertificateFromItem(q
, item
);
1344 require_quiet(certRef
, out
);
1345 require_quiet(_FilterWithPolicy(q
->q_match_policy
, q
->q_match_valid_on_date
, certRef
), out
);
1348 if (q
->q_match_valid_on_date
&& (q
->q_class
== identity_class() || q
->q_class
== cert_class())) {
1350 certRef
= CopyCertificateFromItem(q
, item
);
1351 require_quiet(certRef
, out
);
1352 require_quiet(_FilterWithDate(q
->q_match_valid_on_date
, certRef
), out
);
1355 if (q
->q_match_trusted_only
&& (q
->q_class
== identity_class() || q
->q_class
== cert_class())) {
1357 certRef
= CopyCertificateFromItem(q
, item
);
1358 require_quiet(certRef
, out
);
1359 require_quiet(_FilterWithTrust(CFBooleanGetValue(q
->q_match_trusted_only
), certRef
), out
);
1362 /* Add future match checks here. */
1365 CFReleaseSafe(certRef
);
1369 /****************************************************************************
1370 **************** Beginning of Externally Callable Interface ****************
1371 ****************************************************************************/
1373 void (*SecTaskDiagnoseEntitlements
)(CFArrayRef accessGroups
) = NULL
;
1375 static bool SecEntitlementError(OSStatus status
, CFErrorRef
*error
)
1378 #define SEC_ENTITLEMENT_WARNING CFSTR("com.apple.application-identifier, com.apple.security.application-groups nor keychain-access-groups")
1380 #define SEC_ENTITLEMENT_WARNING CFSTR("application-identifier nor keychain-access-groups")
1383 return SecError(errSecMissingEntitlement
, error
, CFSTR("Client has neither %@ entitlements"), SEC_ENTITLEMENT_WARNING
);
1386 static CFStringRef
CopyAccessGroupForRowID(sqlite_int64 rowID
, CFStringRef itemClass
)
1388 __block CFStringRef accessGroup
= NULL
;
1390 __block CFErrorRef error
= NULL
;
1391 bool ok
= kc_with_dbt(false, &error
, ^bool(SecDbConnectionRef dbt
) {
1392 CFStringRef table
= CFEqual(itemClass
, kSecClassIdentity
) ? kSecClassCertificate
: itemClass
;
1393 CFStringRef sql
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("SELECT agrp FROM %@ WHERE rowid == %u"), table
, (unsigned int)rowID
);
1394 bool dbOk
= SecDbWithSQL(dbt
, sql
, &error
, ^bool(sqlite3_stmt
*stmt
) {
1395 bool rowOk
= SecDbForEach(dbt
, stmt
, &error
, ^bool(int row_index
) {
1396 accessGroup
= CFStringCreateWithBytes(NULL
, sqlite3_column_blob(stmt
, 0), sqlite3_column_bytes(stmt
, 0), kCFStringEncodingUTF8
, false);
1397 return accessGroup
!= NULL
;
1400 return (bool)(rowOk
&& accessGroup
!= NULL
);
1404 return (bool)(dbOk
&& accessGroup
);
1411 CFReleaseNull(accessGroup
);
1416 /* AUDIT[securityd](done):
1417 query (ok) is a caller provided dictionary, only its cf type has been checked.
1420 SecItemServerCopyMatching(CFDictionaryRef query
, CFTypeRef
*result
,
1421 SecurityClient
*client
, CFErrorRef
*error
)
1423 CFArrayRef accessGroups
= client
->accessGroups
;
1424 CFMutableArrayRef mutableAccessGroups
= NULL
;
1427 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
))) {
1428 if (SecTaskDiagnoseEntitlements
)
1429 SecTaskDiagnoseEntitlements(accessGroups
);
1430 return SecEntitlementError(errSecMissingEntitlement
, error
);
1433 if (client
->canAccessNetworkExtensionAccessGroups
) {
1434 CFDataRef persistentRef
= CFDictionaryGetValue(query
, kSecValuePersistentRef
);
1435 CFStringRef itemClass
= NULL
;
1436 sqlite_int64 itemRowID
= 0;
1437 if (persistentRef
&& _SecItemParsePersistentRef(persistentRef
, &itemClass
, &itemRowID
, NULL
)) {
1438 CFStringRef accessGroup
= CopyAccessGroupForRowID(itemRowID
, itemClass
);
1439 if (accessGroup
&& CFStringHasSuffix(accessGroup
, kSecNetworkExtensionAccessGroupSuffix
) && !CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), accessGroup
)) {
1440 mutableAccessGroups
= CFArrayCreateMutableCopy(NULL
, 0, accessGroups
);
1441 CFArrayAppendValue(mutableAccessGroups
, accessGroup
);
1442 accessGroups
= mutableAccessGroups
;
1444 CFReleaseNull(accessGroup
);
1448 if (CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), CFSTR("*"))) {
1449 /* Having the special accessGroup "*" allows access to all accessGroups. */
1450 accessGroups
= NULL
;
1454 Query
*q
= query_create_with_limit(query
, client
->musr
, 1, error
);
1456 CFStringRef agrp
= CFDictionaryGetValue(q
->q_item
, kSecAttrAccessGroup
);
1457 if (agrp
&& accessGroupsAllows(accessGroups
, agrp
, client
)) {
1458 // TODO: Return an error if agrp is not NULL and accessGroupsAllows() fails above.
1459 const void *val
= agrp
;
1460 accessGroups
= CFArrayCreate(0, &val
, 1, &kCFTypeArrayCallBacks
);
1462 CFRetainSafe(accessGroups
);
1465 #if TARGET_OS_IPHONE
1466 if (q
->q_sync_bubble
&& client
->inMultiUser
) {
1467 CFReleaseNull(q
->q_musrView
);
1468 q
->q_musrView
= SecMUSRCreateSyncBubbleUserUUID(q
->q_sync_bubble
);
1469 } else if (client
->inMultiUser
&& client
->isNetworkExtension
) {
1470 CFReleaseNull(q
->q_musrView
);
1471 q
->q_musrView
= SecMUSRCreateBothUserAndSystemUUID(client
->uid
);
1472 } else if (q
->q_system_keychain
&& client
->inMultiUser
) {
1473 CFReleaseNull(q
->q_musrView
);
1474 q
->q_musrView
= SecMUSRCopySystemKeychainUUID();
1476 q
->q_system_keychain
= false;
1480 query_set_caller_access_groups(q
, accessGroups
);
1482 /* Sanity check the query. */
1483 if (q
->q_system_keychain
&& !client
->allowSystemKeychain
) {
1484 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for system keychain"));
1485 } else if (q
->q_sync_bubble
&& !client
->allowSyncBubbleKeychain
) {
1486 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for syncbubble keychain"));
1487 } else if (q
->q_system_keychain
&& q
->q_sync_bubble
) {
1488 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("can't do both system and syncbubble keychain"));
1489 } else if (q
->q_use_item_list
) {
1490 ok
= SecError(errSecUseItemListUnsupported
, error
, CFSTR("use item list unsupported"));
1491 } else if (q
->q_match_issuer
&& ((q
->q_class
!= cert_class()) &&
1492 (q
->q_class
!= identity_class()))) {
1493 ok
= SecError(errSecUnsupportedOperation
, error
, CFSTR("unsupported match attribute"));
1494 } else if (q
->q_match_policy
&& ((q
->q_class
!= cert_class()) &&
1495 (q
->q_class
!= identity_class()))) {
1496 ok
= SecError(errSecUnsupportedOperation
, error
, CFSTR("unsupported kSecMatchPolicy attribute"));
1497 } else if (q
->q_return_type
!= 0 && result
== NULL
) {
1498 ok
= SecError(errSecReturnMissingPointer
, error
, CFSTR("missing pointer"));
1499 } else if (!q
->q_error
) {
1500 ok
= kc_with_dbt(false, error
, ^(SecDbConnectionRef dbt
) {
1501 return s3dl_copy_matching(dbt
, q
, result
, accessGroups
, error
);
1505 CFReleaseSafe(accessGroups
);
1506 if (!query_destroy(q
, error
))
1509 CFReleaseNull(mutableAccessGroups
);
1515 _SecItemCopyMatching(CFDictionaryRef query
, SecurityClient
*client
, CFTypeRef
*result
, CFErrorRef
*error
) {
1516 return SecItemServerCopyMatching(query
, result
, client
, error
);
1519 #if TARGET_OS_IPHONE
1521 SecItemSynchronizable(CFDictionaryRef query
)
1523 bool result
= false;
1524 CFTypeRef value
= CFDictionaryGetValue(query
, kSecAttrSynchronizable
);
1525 if (isBoolean(value
))
1526 return CFBooleanGetValue(value
);
1527 else if (isNumber(value
)) {
1529 (void)CFNumberGetValue(value
, kCFNumberSInt32Type
, &number
);
1538 /* AUDIT[securityd](done):
1539 attributes (ok) is a caller provided dictionary, only its cf type has
1543 _SecItemAdd(CFDictionaryRef attributes
, SecurityClient
*client
, CFTypeRef
*result
, CFErrorRef
*error
)
1545 CFArrayRef accessGroups
= client
->accessGroups
;
1549 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
))) {
1550 if (SecTaskDiagnoseEntitlements
)
1551 SecTaskDiagnoseEntitlements(accessGroups
);
1552 return SecEntitlementError(errSecMissingEntitlement
, error
);
1555 Query
*q
= query_create_with_limit(attributes
, client
->musr
, 0, error
);
1557 /* Access group sanity checking. */
1558 CFStringRef agrp
= (CFStringRef
)CFDictionaryGetValue(attributes
,
1559 kSecAttrAccessGroup
);
1561 /* Having the special accessGroup "*" allows access to all accessGroups. */
1562 if (CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), CFSTR("*")))
1563 accessGroups
= NULL
;
1566 /* The user specified an explicit access group, validate it. */
1567 if (!accessGroupsAllows(accessGroups
, agrp
, client
))
1568 ok
= SecError(errSecMissingEntitlement
, error
,
1569 CFSTR("explicit accessGroup %@ not in client access %@"), agrp
, accessGroups
);
1571 agrp
= (CFStringRef
)CFArrayGetValueAtIndex(client
->accessGroups
, 0);
1573 /* We are using an implicit access group, add it as if the user
1574 specified it as an attribute. */
1575 query_add_attribute(kSecAttrAccessGroup
, agrp
, q
);
1578 if (CFEqual(agrp
, kSecAttrAccessGroupToken
)) {
1579 ok
= SecError(errSecParam
, error
, CFSTR("storing items into kSecAttrAccessGroupToken is not allowed"));
1582 if (SecPLShouldLogRegisteredEvent(CFSTR("SecItem"))) {
1583 CFDictionaryRef dict
= CFDictionaryCreateForCFTypes(NULL
, CFSTR("operation"), CFSTR("add"), CFSTR("AccessGroup"), agrp
, NULL
);
1585 SecPLLogRegisteredEvent(CFSTR("SecItem"), dict
);
1589 #if TARGET_OS_IPHONE
1590 if (q
->q_system_keychain
&& client
->inMultiUser
) {
1591 CFReleaseNull(q
->q_musrView
);
1592 q
->q_musrView
= SecMUSRCopySystemKeychainUUID();
1594 q
->q_system_keychain
= false;
1596 query_add_attribute_with_desc(&v8musr
, q
->q_musrView
, q
);
1600 query_ensure_access_control(q
, agrp
);
1603 void (^add_sync_callback
)(bool, CFErrorRef
) = CFDictionaryGetValue(attributes
, CFSTR("f_ckkscallback"));
1604 if(add_sync_callback
) {
1605 // The existence of this callback indicates that we need a predictable UUID for this item.
1606 q
->q_uuid_from_primary_key
= true;
1607 q
->q_add_sync_callback
= add_sync_callback
;
1611 if (q
->q_system_keychain
&& !client
->allowSystemKeychain
) {
1612 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for system keychain"));
1613 } else if (q
->q_sync_bubble
&& !client
->allowSyncBubbleKeychain
) {
1614 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for syncbubble keychain"));
1615 #if TARGET_OS_IPHONE
1616 } else if (q
->q_system_keychain
&& SecItemSynchronizable(attributes
) && !client
->inMultiUser
) {
1617 ok
= SecError(errSecInvalidKey
, error
, CFSTR("Can't store system keychain and synchronizable"));
1619 } else if (q
->q_row_id
|| q
->q_token_object_id
) {
1620 ok
= SecError(errSecValuePersistentRefUnsupported
, error
, CFSTR("q_row_id")); // TODO: better error string
1621 } else if (!q
->q_error
) {
1622 ok
= kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
){
1623 return kc_transaction(dbt
, error
, ^{
1624 query_pre_add(q
, true);
1625 return s3dl_query_add(dbt
, q
, result
, error
);
1630 ok
= query_notify_and_destroy(q
, ok
, error
);
1637 /* AUDIT[securityd](done):
1638 query (ok) and attributesToUpdate (ok) are a caller provided dictionaries,
1639 only their cf types have been checked.
1642 _SecItemUpdate(CFDictionaryRef query
, CFDictionaryRef attributesToUpdate
,
1643 SecurityClient
*client
, CFErrorRef
*error
)
1645 CFArrayRef accessGroups
= client
->accessGroups
;
1648 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
))) {
1649 if (SecTaskDiagnoseEntitlements
)
1650 SecTaskDiagnoseEntitlements(accessGroups
);
1651 return SecEntitlementError(errSecMissingEntitlement
, error
);
1654 if (SecPLShouldLogRegisteredEvent(CFSTR("SecItem"))) {
1655 CFTypeRef agrp
= CFArrayGetValueAtIndex(accessGroups
, 0);
1656 CFDictionaryRef dict
= CFDictionaryCreateForCFTypes(NULL
, CFSTR("operation"), CFSTR("update"), CFSTR("AccessGroup"), agrp
, NULL
);
1658 SecPLLogRegisteredEvent(CFSTR("SecItem"), dict
);
1663 if (CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), CFSTR("*"))) {
1664 /* Having the special accessGroup "*" allows access to all accessGroups. */
1665 accessGroups
= NULL
;
1669 Query
*q
= query_create_with_limit(query
, client
->musr
, kSecMatchUnlimited
, error
);
1674 #if TARGET_OS_IPHONE
1675 if (q
->q_system_keychain
&& client
->inMultiUser
) {
1676 CFReleaseNull(q
->q_musrView
);
1677 q
->q_musrView
= SecMUSRCopySystemKeychainUUID();
1679 q
->q_system_keychain
= false;
1683 /* Sanity check the query. */
1684 query_set_caller_access_groups(q
, accessGroups
);
1685 if (q
->q_system_keychain
&& !client
->allowSystemKeychain
) {
1686 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for system keychain"));
1687 } else if (q
->q_sync_bubble
&& !client
->allowSyncBubbleKeychain
) {
1688 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for syncbubble keychain"));
1689 #if TARGET_OS_IPHONE
1690 } else if (q
->q_system_keychain
&& SecItemSynchronizable(attributesToUpdate
) && !client
->inMultiUser
) {
1691 ok
= SecError(errSecInvalidKey
, error
, CFSTR("Can't update an system keychain item with synchronizable"));
1693 } else if (q
->q_use_item_list
) {
1694 ok
= SecError(errSecUseItemListUnsupported
, error
, CFSTR("use item list not supported"));
1695 } else if (q
->q_return_type
& kSecReturnDataMask
) {
1696 /* Update doesn't return anything so don't ask for it. */
1697 ok
= SecError(errSecReturnDataUnsupported
, error
, CFSTR("return data not supported by update"));
1698 } else if (q
->q_return_type
& kSecReturnAttributesMask
) {
1699 ok
= SecError(errSecReturnAttributesUnsupported
, error
, CFSTR("return attributes not supported by update"));
1700 } else if (q
->q_return_type
& kSecReturnRefMask
) {
1701 ok
= SecError(errSecReturnRefUnsupported
, error
, CFSTR("return ref not supported by update"));
1702 } else if (q
->q_return_type
& kSecReturnPersistentRefMask
) {
1703 ok
= SecError(errSecReturnPersistentRefUnsupported
, error
, CFSTR("return persistent ref not supported by update"));
1705 /* Access group sanity checking. */
1706 CFStringRef agrp
= (CFStringRef
)CFDictionaryGetValue(attributesToUpdate
,
1707 kSecAttrAccessGroup
);
1709 /* The user is attempting to modify the access group column,
1710 validate it to make sure the new value is allowable. */
1711 if (CFEqual(agrp
, kSecAttrAccessGroupToken
)) {
1712 ok
= SecError(errSecParam
, error
, CFSTR("storing items into kSecAttrAccessGroupToken is not allowed"));
1714 if (!accessGroupsAllows(accessGroups
, agrp
, client
)) {
1715 ok
= SecError(errSecNoAccessForItem
, error
, CFSTR("accessGroup %@ not in %@"), agrp
, accessGroups
);
1721 ok
= kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
1722 return kc_transaction(dbt
, error
, ^{
1723 return s3dl_query_update(dbt
, q
, attributesToUpdate
, accessGroups
, error
);
1728 ok
= query_notify_and_destroy(q
, ok
, error
);
1734 /* AUDIT[securityd](done):
1735 query (ok) is a caller provided dictionary, only its cf type has been checked.
1738 _SecItemDelete(CFDictionaryRef query
, SecurityClient
*client
, CFErrorRef
*error
)
1740 CFArrayRef accessGroups
= client
->accessGroups
;
1743 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
))) {
1744 if (SecTaskDiagnoseEntitlements
)
1745 SecTaskDiagnoseEntitlements(accessGroups
);
1746 return SecEntitlementError(errSecMissingEntitlement
, error
);
1749 if (SecPLShouldLogRegisteredEvent(CFSTR("SecItem"))) {
1750 CFTypeRef agrp
= CFArrayGetValueAtIndex(accessGroups
, 0);
1751 CFDictionaryRef dict
= CFDictionaryCreateForCFTypes(NULL
, CFSTR("operation"), CFSTR("delete"), CFSTR("AccessGroup"), agrp
, NULL
);
1753 SecPLLogRegisteredEvent(CFSTR("SecItem"), dict
);
1758 if (CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), CFSTR("*"))) {
1759 /* Having the special accessGroup "*" allows access to all accessGroups. */
1760 accessGroups
= NULL
;
1763 Query
*q
= query_create_with_limit(query
, client
->musr
, kSecMatchUnlimited
, error
);
1766 #if TARGET_OS_IPHONE
1767 if (q
->q_system_keychain
&& client
->inMultiUser
) {
1768 CFReleaseNull(q
->q_musrView
);
1769 q
->q_musrView
= SecMUSRCopySystemKeychainUUID();
1771 q
->q_system_keychain
= false;
1775 query_set_caller_access_groups(q
, accessGroups
);
1776 /* Sanity check the query. */
1777 if (q
->q_system_keychain
&& !client
->allowSystemKeychain
) {
1778 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for system keychain"));
1779 } else if (q
->q_sync_bubble
&& !client
->allowSyncBubbleKeychain
) {
1780 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for syncbubble keychain"));
1781 } else if (q
->q_limit
!= kSecMatchUnlimited
) {
1782 ok
= SecError(errSecMatchLimitUnsupported
, error
, CFSTR("match limit not supported by delete"));
1783 } else if (query_match_count(q
) != 0) {
1784 ok
= SecError(errSecItemMatchUnsupported
, error
, CFSTR("match not supported by delete"));
1785 } else if (q
->q_ref
) {
1786 ok
= SecError(errSecValueRefUnsupported
, error
, CFSTR("value ref not supported by delete"));
1787 } else if (q
->q_row_id
&& query_attr_count(q
)) {
1788 ok
= SecError(errSecItemIllegalQuery
, error
, CFSTR("rowid and other attributes are mutually exclusive"));
1789 } else if (q
->q_token_object_id
&& query_attr_count(q
) != 1) {
1790 ok
= SecError(errSecItemIllegalQuery
, error
, CFSTR("token persistent ref and other attributes are mutually exclusive"));
1792 ok
= kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
1793 return kc_transaction(dbt
, error
, ^{
1794 return s3dl_query_delete(dbt
, q
, accessGroups
, error
);
1798 ok
= query_notify_and_destroy(q
, ok
, error
);
1805 static bool SecItemDeleteTokenItems(SecDbConnectionRef dbt
, CFTypeRef classToDelete
, CFTypeRef tokenID
, CFArrayRef accessGroups
, SecurityClient
*client
, CFErrorRef
*error
) {
1806 CFTypeRef keys
[] = { kSecClass
, kSecAttrTokenID
};
1807 CFTypeRef values
[] = { classToDelete
, tokenID
};
1809 CFDictionaryRef query
= CFDictionaryCreate(kCFAllocatorDefault
, keys
, values
, 2, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1810 Query
*q
= query_create_with_limit(query
, client
->musr
, kSecMatchUnlimited
, error
);
1814 query_set_caller_access_groups(q
, accessGroups
);
1815 ok
= s3dl_query_delete(dbt
, q
, accessGroups
, error
);
1816 ok
= query_notify_and_destroy(q
, ok
, error
);
1824 static bool SecItemAddTokenItem(SecDbConnectionRef dbt
, CFDictionaryRef attributes
, CFArrayRef accessGroups
, SecurityClient
*client
, CFErrorRef
*error
) {
1826 Query
*q
= query_create_with_limit(attributes
, client
->musr
, 0, error
);
1828 CFStringRef agrp
= kSecAttrAccessGroupToken
;
1829 query_add_attribute(kSecAttrAccessGroup
, agrp
, q
);
1832 query_ensure_access_control(q
, agrp
);
1833 if (q
->q_system_keychain
&& !client
->allowSystemKeychain
) {
1834 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for system keychain"));
1835 } else if (q
->q_sync_bubble
&& !client
->allowSyncBubbleKeychain
) {
1836 ok
= SecError(errSecMissingEntitlement
, error
, CFSTR("client doesn't have entitlement for syncbubble keychain"));
1837 } else if (q
->q_row_id
|| q
->q_token_object_id
) {
1838 ok
= SecError(errSecValuePersistentRefUnsupported
, error
, CFSTR("q_row_id")); // TODO: better error string
1839 } else if (!q
->q_error
) {
1840 query_pre_add(q
, true);
1841 ok
= s3dl_query_add(dbt
, q
, NULL
, error
);
1844 ok
= query_notify_and_destroy(q
, ok
, error
);
1851 bool _SecItemUpdateTokenItems(CFStringRef tokenID
, CFArrayRef items
, SecurityClient
*client
, CFErrorRef
*error
) {
1853 CFArrayRef accessGroups
= client
->accessGroups
;
1855 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
))) {
1856 if (SecTaskDiagnoseEntitlements
)
1857 SecTaskDiagnoseEntitlements(accessGroups
);
1858 return SecEntitlementError(errSecMissingEntitlement
, error
);
1861 ok
= kc_with_dbt(true, error
, ^bool (SecDbConnectionRef dbt
) {
1862 return kc_transaction(dbt
, error
, ^bool {
1864 const CFTypeRef classToDelete
[] = { kSecClassGenericPassword
, kSecClassInternetPassword
, kSecClassCertificate
, kSecClassKey
};
1865 for (size_t i
= 0; i
< sizeof(classToDelete
) / sizeof(classToDelete
[0]); ++i
) {
1866 SecItemDeleteTokenItems(dbt
, classToDelete
[i
], tokenID
, accessGroups
, client
, NULL
);
1869 for (CFIndex i
= 0; i
< CFArrayGetCount(items
); ++i
) {
1870 if (!SecItemAddTokenItem(dbt
, CFArrayGetValueAtIndex(items
, i
), accessGroups
, client
, error
))
1876 const CFTypeRef classToDelete
[] = { kSecClassGenericPassword
, kSecClassInternetPassword
, kSecClassCertificate
, kSecClassKey
};
1877 bool deleted
= true;
1878 for (size_t i
= 0; i
< sizeof(classToDelete
) / sizeof(classToDelete
[0]); ++i
) {
1879 if (!SecItemDeleteTokenItems(dbt
, classToDelete
[i
], tokenID
, accessGroups
, client
, error
) && error
&& CFErrorGetCode(*error
) != errSecItemNotFound
) {
1883 else if (error
&& *error
) {
1884 CFReleaseNull(*error
);
1895 /* AUDIT[securityd](done):
1896 No caller provided inputs.
1899 SecItemServerDeleteAll(CFErrorRef
*error
) {
1900 return kc_with_dbt(true, error
, ^bool (SecDbConnectionRef dbt
) {
1901 return (kc_transaction(dbt
, error
, ^bool {
1902 return (SecDbExec(dbt
, CFSTR("DELETE from genp;"), error
) &&
1903 SecDbExec(dbt
, CFSTR("DELETE from inet;"), error
) &&
1904 SecDbExec(dbt
, CFSTR("DELETE from cert;"), error
) &&
1905 SecDbExec(dbt
, CFSTR("DELETE from keys;"), error
));
1906 }) && SecDbExec(dbt
, CFSTR("VACUUM;"), error
));
1911 _SecItemDeleteAll(CFErrorRef
*error
) {
1912 return SecItemServerDeleteAll(error
);
1916 _SecItemServerDeleteAllWithAccessGroups(CFArrayRef accessGroups
, SecurityClient
*client
, CFErrorRef
*error
)
1918 __block
bool ok
= true;
1919 static dispatch_once_t onceToken
;
1920 static CFSetRef illegalAccessGroups
= NULL
;
1922 dispatch_once(&onceToken
, ^{
1923 const CFStringRef values
[] = {
1926 CFSTR("com.apple.security.sos"),
1927 CFSTR("lockdown-identities"),
1929 illegalAccessGroups
= CFSetCreate(NULL
, (const void **)values
, sizeof(values
)/sizeof(values
[0]), &kCFTypeSetCallBacks
);
1932 static CFTypeRef qclasses
[] = {
1938 // strange construction needed for schema indirection
1939 static dispatch_once_t qclassesOnceToken
;
1940 dispatch_once(&qclassesOnceToken
, ^{
1941 qclasses
[0] = inet_class();
1942 qclasses
[1] = genp_class();
1943 qclasses
[2] = keys_class();
1944 qclasses
[3] = cert_class();
1947 require_action_quiet(isArray(accessGroups
), fail
,
1949 SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType
, sSecXPCErrorDomain
, NULL
, error
, NULL
, CFSTR("accessGroups not CFArray, got %@"), accessGroups
));
1951 // TODO: whitelist instead? look for dev IDs like 7123498YQX.com.somedev.app
1953 require_action(CFArrayGetCount(accessGroups
) != 0, fail
,
1955 SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType
, sSecXPCErrorDomain
, NULL
, error
, NULL
, CFSTR("accessGroups e empty")));
1958 // Pre-check accessGroups for prohibited values
1959 CFArrayForEach(accessGroups
, ^(const void *value
) {
1960 CFStringRef agrp
= (CFStringRef
)value
;
1962 if (!isString(agrp
)) {
1963 SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType
, sSecXPCErrorDomain
, NULL
, error
, NULL
,
1964 CFSTR("access not a string: %@"), agrp
);
1966 } else if (CFSetContainsValue(illegalAccessGroups
, agrp
)) {
1967 SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType
, sSecXPCErrorDomain
, NULL
, error
, NULL
,
1968 CFSTR("illegal access group: %@"), accessGroups
);
1974 ok
= kc_with_dbt(true, error
, ^bool(SecDbConnectionRef dbt
) {
1975 return kc_transaction(dbt
, error
, ^bool {
1976 CFErrorRef localError
= NULL
;
1980 for (n
= 0; n
< sizeof(qclasses
)/sizeof(qclasses
[0]) && ok1
; n
++) {
1983 q
= query_create(qclasses
[n
], client
->musr
, NULL
, error
);
1986 (void)s3dl_query_delete(dbt
, q
, accessGroups
, &localError
);
1988 query_destroy(q
, error
);
1989 CFReleaseNull(localError
);
1992 }) && SecDbExec(dbt
, CFSTR("VACUUM"), error
);
2001 // MARK: Shared web credentials
2003 #if TARGET_OS_IOS && !TARGET_OS_BRIDGE
2006 #define SEC_CONST_DECL(k,v) const CFStringRef k = CFSTR(v);
2008 SEC_CONST_DECL (kSecSafariAccessGroup
, "com.apple.cfnetwork");
2009 SEC_CONST_DECL (kSecSafariDefaultComment
, "default");
2010 SEC_CONST_DECL (kSecSafariPasswordsNotSaved
, "Passwords not saved");
2011 SEC_CONST_DECL (kSecSharedCredentialUrlScheme
, "https://");
2012 SEC_CONST_DECL (kSecSharedWebCredentialsService
, "webcredentials");
2014 #if !TARGET_IPHONE_SIMULATOR
2016 _SecAppDomainApprovalStatus(CFStringRef appID
, CFStringRef fqdn
, CFErrorRef
*error
)
2018 __block SWCFlags flags
= kSWCFlags_None
;
2021 dispatch_semaphore_t semaphore
= dispatch_semaphore_create(0);
2022 if (semaphore
== NULL
)
2025 status
= SWCCheckService(kSecSharedWebCredentialsService
, appID
, fqdn
, ^void (OSStatus inStatus
, SWCFlags inFlags
, CFDictionaryRef inDetails
)
2027 if (inStatus
== 0) {
2030 secerror("SWCCheckService failed with %d", (int)inStatus
);
2032 dispatch_semaphore_signal(semaphore
);
2036 dispatch_semaphore_wait(semaphore
, DISPATCH_TIME_FOREVER
);
2038 secerror("SWCCheckService: failed to queue");
2040 dispatch_release(semaphore
);
2043 if (!(flags
& kSWCFlag_SiteApproved
)) {
2044 if (flags
& kSWCFlag_Pending
) {
2045 SecError(errSecAuthFailed
, error
, CFSTR("Approval is pending for \"%@\", try later"), fqdn
);
2047 SecError(errSecAuthFailed
, error
, CFSTR("\"%@\" failed to approve \"%@\""), fqdn
, appID
);
2049 } else if (flags
& kSWCFlag_UserDenied
) {
2050 SecError(errSecAuthFailed
, error
, CFSTR("User denied access to \"%@\" by \"%@\""), fqdn
, appID
);
2057 _SecEntitlementContainsDomainForService(CFArrayRef domains
, CFStringRef domain
, CFStringRef service
)
2059 bool result
= false;
2060 CFIndex idx
, count
= (domains
) ? CFArrayGetCount(domains
) : (CFIndex
) 0;
2061 if (!count
|| !domain
|| !service
) {
2064 for (idx
=0; idx
< count
; idx
++) {
2065 CFStringRef str
= (CFStringRef
) CFArrayGetValueAtIndex(domains
, idx
);
2066 if (str
&& CFStringHasPrefix(str
, kSecSharedWebCredentialsService
)) {
2067 CFIndex prefix_len
= CFStringGetLength(kSecSharedWebCredentialsService
)+1;
2068 CFIndex substr_len
= CFStringGetLength(str
) - prefix_len
;
2069 CFRange range
= { prefix_len
, substr_len
};
2070 CFStringRef substr
= CFStringCreateWithSubstring(kCFAllocatorDefault
, str
, range
);
2071 if (substr
&& CFEqual(substr
, domain
)) {
2074 CFReleaseSafe(substr
);
2082 #endif /* !TARGET_OS_SIMULATOR */
2085 _SecAddNegativeWebCredential(SecurityClient
*client
, CFStringRef fqdn
, CFStringRef appID
, bool forSafari
)
2087 #if !TARGET_IPHONE_SIMULATOR
2088 bool result
= false;
2089 if (!fqdn
) { return result
; }
2091 // update our database
2092 CFRetainSafe(appID
);
2094 if (0 == SWCSetServiceFlags(kSecSharedWebCredentialsService
, appID
, fqdn
, kSWCFlag_ExternalMask
, kSWCFlag_UserDenied
,
2095 ^void(OSStatus inStatus
, SWCFlags inNewFlags
){
2096 CFReleaseSafe(appID
);
2097 CFReleaseSafe(fqdn
);
2102 else // didn't queue the block
2104 CFReleaseSafe(appID
);
2105 CFReleaseSafe(fqdn
);
2108 if (!forSafari
) { return result
; }
2110 // below this point: create a negative Safari web credential item
2112 CFMutableDictionaryRef attrs
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2113 if (!attrs
) { return result
; }
2115 CFErrorRef error
= NULL
;
2116 CFStringRef accessGroup
= CFSTR("*");
2117 SecurityClient swcclient
= {
2119 .accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&accessGroup
, 1, &kCFTypeArrayCallBacks
),
2120 .allowSystemKeychain
= false,
2121 .allowSyncBubbleKeychain
= false,
2122 .isNetworkExtension
= false,
2123 .musr
= client
->musr
,
2126 CFDictionaryAddValue(attrs
, kSecClass
, kSecClassInternetPassword
);
2127 CFDictionaryAddValue(attrs
, kSecAttrAccessGroup
, kSecSafariAccessGroup
);
2128 CFDictionaryAddValue(attrs
, kSecAttrAuthenticationType
, kSecAttrAuthenticationTypeHTMLForm
);
2129 CFDictionaryAddValue(attrs
, kSecAttrProtocol
, kSecAttrProtocolHTTPS
);
2130 CFDictionaryAddValue(attrs
, kSecAttrServer
, fqdn
);
2131 CFDictionaryAddValue(attrs
, kSecAttrSynchronizable
, kCFBooleanTrue
);
2133 (void)_SecItemDelete(attrs
, &swcclient
, &error
);
2134 CFReleaseNull(error
);
2136 CFDictionaryAddValue(attrs
, kSecAttrAccount
, kSecSafariPasswordsNotSaved
);
2137 CFDictionaryAddValue(attrs
, kSecAttrComment
, kSecSafariDefaultComment
);
2139 CFStringRef label
= CFStringCreateWithFormat(kCFAllocatorDefault
,
2140 NULL
, CFSTR("%@ (%@)"), fqdn
, kSecSafariPasswordsNotSaved
);
2142 CFDictionaryAddValue(attrs
, kSecAttrLabel
, label
);
2143 CFReleaseSafe(label
);
2147 CFDataRef data
= CFDataCreate(kCFAllocatorDefault
, &space
, 1);
2149 CFDictionarySetValue(attrs
, kSecValueData
, data
);
2150 CFReleaseSafe(data
);
2153 CFTypeRef addResult
= NULL
;
2154 result
= _SecItemAdd(attrs
, &swcclient
, &addResult
, &error
);
2156 CFReleaseSafe(addResult
);
2157 CFReleaseSafe(error
);
2158 CFReleaseSafe(attrs
);
2159 CFReleaseSafe(swcclient
.accessGroups
);
2167 /* Specialized version of SecItemAdd for shared web credentials */
2169 _SecAddSharedWebCredential(CFDictionaryRef attributes
,
2170 SecurityClient
*client
,
2171 const audit_token_t
*clientAuditToken
,
2178 SecurityClient swcclient
= {};
2180 CFStringRef fqdn
= CFRetainSafe(CFDictionaryGetValue(attributes
, kSecAttrServer
));
2181 CFStringRef account
= CFDictionaryGetValue(attributes
, kSecAttrAccount
);
2182 #if TARGET_OS_IOS && !TARGET_OS_BRIDGE
2183 CFStringRef password
= CFDictionaryGetValue(attributes
, kSecSharedPassword
);
2185 CFStringRef password
= CFDictionaryGetValue(attributes
, CFSTR("spwd"));
2187 CFStringRef accessGroup
= CFSTR("*");
2188 CFMutableDictionaryRef query
= NULL
, attrs
= NULL
;
2192 // check autofill enabled status
2193 if (!swca_autofill_enabled(clientAuditToken
)) {
2194 SecError(errSecBadReq
, error
, CFSTR("Autofill is not enabled in Safari settings"));
2198 // parse fqdn with CFURL here, since it could be specified as domain:port
2200 CFStringRef urlStr
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%@%@"), kSecSharedCredentialUrlScheme
, fqdn
);
2202 CFURLRef url
= CFURLCreateWithString(kCFAllocatorDefault
, urlStr
, nil
);
2204 CFStringRef hostname
= CFURLCopyHostName(url
);
2206 CFReleaseSafe(fqdn
);
2208 port
= CFURLGetPortNumber(url
);
2212 CFReleaseSafe(urlStr
);
2217 SecError(errSecParam
, error
, CFSTR("No account provided"));
2221 SecError(errSecParam
, error
, CFSTR("No domain provided"));
2225 #if TARGET_IPHONE_SIMULATOR
2226 secerror("app/site association entitlements not checked in Simulator");
2228 OSStatus status
= errSecMissingEntitlement
;
2229 // validate that fqdn is part of caller's shared credential domains entitlement
2231 SecError(status
, error
, CFSTR("Missing application-identifier entitlement"));
2234 if (_SecEntitlementContainsDomainForService(domains
, fqdn
, kSecSharedWebCredentialsService
)) {
2235 status
= errSecSuccess
;
2237 if (errSecSuccess
!= status
) {
2238 CFStringRef msg
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
,
2239 CFSTR("%@ not found in %@ entitlement"), fqdn
, kSecEntitlementAssociatedDomains
);
2241 msg
= CFRetain(CFSTR("Requested domain not found in entitlement"));
2243 SecError(status
, error
, CFSTR("%@"), msg
);
2249 #if TARGET_IPHONE_SIMULATOR
2250 secerror("Ignoring app/site approval state in the Simulator.");
2252 // get approval status for this app/domain pair
2253 SWCFlags flags
= _SecAppDomainApprovalStatus(appID
, fqdn
, error
);
2254 if (!(flags
& kSWCFlag_SiteApproved
)) {
2259 // give ourselves access to see matching items for kSecSafariAccessGroup
2260 swcclient
.task
= NULL
;
2261 swcclient
.accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&accessGroup
, 1, &kCFTypeArrayCallBacks
);
2262 swcclient
.allowSystemKeychain
= false;
2263 swcclient
.musr
= client
->musr
;
2264 swcclient
.allowSystemKeychain
= false;
2265 swcclient
.allowSyncBubbleKeychain
= false;
2266 swcclient
.isNetworkExtension
= false;
2269 // create lookup query
2270 query
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2272 SecError(errSecAllocate
, error
, CFSTR("Unable to create query dictionary"));
2275 CFDictionaryAddValue(query
, kSecClass
, kSecClassInternetPassword
);
2276 CFDictionaryAddValue(query
, kSecAttrAccessGroup
, kSecSafariAccessGroup
);
2277 CFDictionaryAddValue(query
, kSecAttrAuthenticationType
, kSecAttrAuthenticationTypeHTMLForm
);
2278 CFDictionaryAddValue(query
, kSecAttrServer
, fqdn
);
2279 CFDictionaryAddValue(query
, kSecAttrSynchronizable
, kCFBooleanTrue
);
2281 // check for presence of Safari's negative entry ('passwords not saved')
2282 CFDictionarySetValue(query
, kSecAttrAccount
, kSecSafariPasswordsNotSaved
);
2283 ok
= _SecItemCopyMatching(query
, &swcclient
, result
, error
);
2284 if(result
) CFReleaseNull(*result
);
2285 if (error
) CFReleaseNull(*error
);
2287 SecError(errSecDuplicateItem
, error
, CFSTR("Item already exists for this server"));
2291 // now use the provided account (and optional port number, if one was present)
2292 CFDictionarySetValue(query
, kSecAttrAccount
, account
);
2293 if (port
< -1 || port
> 0) {
2294 SInt16 portValueShort
= (port
& 0xFFFF);
2295 CFNumberRef portNumber
= CFNumberCreate(NULL
, kCFNumberSInt16Type
, &portValueShort
);
2296 CFDictionaryAddValue(query
, kSecAttrPort
, portNumber
);
2297 CFReleaseSafe(portNumber
);
2300 // look up existing password
2301 CFDictionaryAddValue(query
, kSecReturnData
, kCFBooleanTrue
);
2302 bool matched
= _SecItemCopyMatching(query
, &swcclient
, result
, error
);
2303 CFDictionaryRemoveValue(query
, kSecReturnData
);
2305 // found it, so this becomes either an "update password" or "delete password" operation
2306 bool update
= (password
!= NULL
);
2308 attrs
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2309 CFDataRef credential
= CFStringCreateExternalRepresentation(kCFAllocatorDefault
, password
, kCFStringEncodingUTF8
, 0);
2310 CFDictionaryAddValue(attrs
, kSecValueData
, credential
);
2311 bool samePassword
= result
&& *result
&& CFEqual(*result
, credential
);
2312 CFReleaseSafe(credential
);
2313 CFDictionaryAddValue(attrs
, kSecAttrComment
, kSecSafariDefaultComment
);
2315 ok
= samePassword
|| swca_confirm_operation(swca_update_request_id
, clientAuditToken
, query
, error
,
2316 ^void (CFStringRef fqdn
) { _SecAddNegativeWebCredential(client
, fqdn
, appID
, false); });
2318 ok
= _SecItemUpdate(query
, attrs
, &swcclient
, error
);
2322 // confirm the delete
2323 // (per rdar://16676288 we always prompt, even if there was prior user approval)
2324 ok
= /*approved ||*/ swca_confirm_operation(swca_delete_request_id
, clientAuditToken
, query
, error
,
2325 ^void (CFStringRef fqdn
) { _SecAddNegativeWebCredential(client
, fqdn
, appID
, false); });
2327 ok
= _SecItemDelete(query
, &swcclient
, error
);
2331 if(result
) CFReleaseNull(*result
);
2332 if(error
) CFReleaseNull(*error
);
2336 if (result
) CFReleaseNull(*result
);
2337 if (error
) CFReleaseNull(*error
);
2339 // password does not exist, so prepare to add it
2341 // a NULL password value removes the existing credential. Since we didn't find it, this is a no-op.
2346 CFStringRef label
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%@ (%@)"), fqdn
, account
);
2348 CFDictionaryAddValue(query
, kSecAttrLabel
, label
);
2349 CFReleaseSafe(label
);
2351 // NOTE: we always expect to use HTTPS for web forms.
2352 CFDictionaryAddValue(query
, kSecAttrProtocol
, kSecAttrProtocolHTTPS
);
2354 CFDataRef credential
= CFStringCreateExternalRepresentation(kCFAllocatorDefault
, password
, kCFStringEncodingUTF8
, 0);
2355 CFDictionarySetValue(query
, kSecValueData
, credential
);
2356 CFReleaseSafe(credential
);
2357 CFDictionarySetValue(query
, kSecAttrComment
, kSecSafariDefaultComment
);
2359 CFReleaseSafe(swcclient
.accessGroups
);
2360 swcclient
.accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&kSecSafariAccessGroup
, 1, &kCFTypeArrayCallBacks
);
2362 // mark the item as created by this function
2363 const int32_t creator_value
= 'swca';
2364 CFNumberRef creator
= CFNumberCreate(kCFAllocatorDefault
, kCFNumberSInt32Type
, &creator_value
);
2366 CFDictionarySetValue(query
, kSecAttrCreator
, creator
);
2367 CFReleaseSafe(creator
);
2372 // (per rdar://16680019, we won't prompt here in the normal case)
2373 ok
= /*approved ||*/ swca_confirm_operation(swca_add_request_id
, clientAuditToken
, query
, error
,
2374 ^void (CFStringRef fqdn
) { _SecAddNegativeWebCredential(client
, fqdn
, appID
, false); });
2378 ok
= _SecItemAdd(query
, &swcclient
, result
, error
);
2382 CFReleaseSafe(attrs
);
2383 CFReleaseSafe(query
);
2384 CFReleaseSafe(swcclient
.accessGroups
);
2385 CFReleaseSafe(fqdn
);
2389 /* Specialized version of SecItemCopyMatching for shared web credentials */
2391 _SecCopySharedWebCredential(CFDictionaryRef query
,
2392 SecurityClient
*client
,
2393 const audit_token_t
*clientAuditToken
,
2399 CFMutableArrayRef credentials
= NULL
;
2400 CFMutableArrayRef foundItems
= NULL
;
2401 CFMutableArrayRef fqdns
= NULL
;
2402 CFStringRef fqdn
= NULL
;
2403 CFStringRef account
= NULL
;
2408 require_quiet(result
, cleanup
);
2409 credentials
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
2410 foundItems
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
2411 fqdns
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
2413 // give ourselves access to see matching items for kSecSafariAccessGroup
2414 CFStringRef accessGroup
= CFSTR("*");
2415 SecurityClient swcclient
= {
2417 .accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&accessGroup
, 1, &kCFTypeArrayCallBacks
),
2418 .allowSystemKeychain
= false,
2419 .allowSyncBubbleKeychain
= false,
2420 .isNetworkExtension
= false,
2421 .musr
= client
->musr
,
2424 // On input, the query dictionary contains optional fqdn and account entries.
2425 fqdn
= CFDictionaryGetValue(query
, kSecAttrServer
);
2426 account
= CFDictionaryGetValue(query
, kSecAttrAccount
);
2428 // Check autofill enabled status
2429 if (!swca_autofill_enabled(clientAuditToken
)) {
2430 SecError(errSecBadReq
, error
, CFSTR("Autofill is not enabled in Safari settings"));
2434 // Check fqdn; if NULL, add domains from caller's entitlement.
2436 CFArrayAppendValue(fqdns
, fqdn
);
2439 CFIndex idx
, count
= CFArrayGetCount(domains
);
2440 for (idx
=0; idx
< count
; idx
++) {
2441 CFStringRef str
= (CFStringRef
) CFArrayGetValueAtIndex(domains
, idx
);
2442 // Parse the entry for our service label prefix
2443 if (str
&& CFStringHasPrefix(str
, kSecSharedWebCredentialsService
)) {
2444 CFIndex prefix_len
= CFStringGetLength(kSecSharedWebCredentialsService
)+1;
2445 CFIndex substr_len
= CFStringGetLength(str
) - prefix_len
;
2446 CFRange range
= { prefix_len
, substr_len
};
2447 fqdn
= CFStringCreateWithSubstring(kCFAllocatorDefault
, str
, range
);
2449 CFArrayAppendValue(fqdns
, fqdn
);
2455 count
= CFArrayGetCount(fqdns
);
2457 SecError(errSecParam
, error
, CFSTR("No domain provided"));
2461 // Aggregate search results for each domain
2462 for (idx
= 0; idx
< count
; idx
++) {
2463 CFMutableArrayRef items
= NULL
;
2464 CFMutableDictionaryRef attrs
= NULL
;
2465 fqdn
= (CFStringRef
) CFArrayGetValueAtIndex(fqdns
, idx
);
2469 // Parse the fqdn for a possible port specifier.
2471 CFStringRef urlStr
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%@%@"), kSecSharedCredentialUrlScheme
, fqdn
);
2473 CFURLRef url
= CFURLCreateWithString(kCFAllocatorDefault
, urlStr
, nil
);
2475 CFStringRef hostname
= CFURLCopyHostName(url
);
2477 CFReleaseSafe(fqdn
);
2479 port
= CFURLGetPortNumber(url
);
2483 CFReleaseSafe(urlStr
);
2487 #if TARGET_IPHONE_SIMULATOR
2488 secerror("app/site association entitlements not checked in Simulator");
2490 OSStatus status
= errSecMissingEntitlement
;
2492 SecError(status
, error
, CFSTR("Missing application-identifier entitlement"));
2493 CFReleaseSafe(fqdn
);
2496 // validate that fqdn is part of caller's entitlement
2497 if (_SecEntitlementContainsDomainForService(domains
, fqdn
, kSecSharedWebCredentialsService
)) {
2498 status
= errSecSuccess
;
2500 if (errSecSuccess
!= status
) {
2501 CFStringRef msg
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
,
2502 CFSTR("%@ not found in %@ entitlement"), fqdn
, kSecEntitlementAssociatedDomains
);
2504 msg
= CFRetain(CFSTR("Requested domain not found in entitlement"));
2506 SecError(status
, error
, CFSTR("%@"), msg
);
2508 CFReleaseSafe(fqdn
);
2513 attrs
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2515 SecError(errSecAllocate
, error
, CFSTR("Unable to create query dictionary"));
2516 CFReleaseSafe(fqdn
);
2519 CFDictionaryAddValue(attrs
, kSecClass
, kSecClassInternetPassword
);
2520 CFDictionaryAddValue(attrs
, kSecAttrAccessGroup
, kSecSafariAccessGroup
);
2521 CFDictionaryAddValue(attrs
, kSecAttrProtocol
, kSecAttrProtocolHTTPS
);
2522 CFDictionaryAddValue(attrs
, kSecAttrAuthenticationType
, kSecAttrAuthenticationTypeHTMLForm
);
2523 CFDictionaryAddValue(attrs
, kSecAttrServer
, fqdn
);
2525 CFDictionaryAddValue(attrs
, kSecAttrAccount
, account
);
2527 if (port
< -1 || port
> 0) {
2528 SInt16 portValueShort
= (port
& 0xFFFF);
2529 CFNumberRef portNumber
= CFNumberCreate(NULL
, kCFNumberSInt16Type
, &portValueShort
);
2530 CFDictionaryAddValue(attrs
, kSecAttrPort
, portNumber
);
2531 CFReleaseSafe(portNumber
);
2533 CFDictionaryAddValue(attrs
, kSecAttrSynchronizable
, kCFBooleanTrue
);
2534 CFDictionaryAddValue(attrs
, kSecMatchLimit
, kSecMatchLimitAll
);
2535 CFDictionaryAddValue(attrs
, kSecReturnAttributes
, kCFBooleanTrue
);
2536 CFDictionaryAddValue(attrs
, kSecReturnData
, kCFBooleanTrue
);
2538 ok
= _SecItemCopyMatching(attrs
, &swcclient
, (CFTypeRef
*)&items
, error
);
2540 // ignore interim error since we have multiple domains to search
2541 CFReleaseNull(*error
);
2543 if (ok
&& items
&& CFGetTypeID(items
) == CFArrayGetTypeID()) {
2544 #if TARGET_IPHONE_SIMULATOR
2545 secerror("Ignoring app/site approval state in the Simulator.");
2546 bool approved
= true;
2548 // get approval status for this app/domain pair
2549 SWCFlags flags
= _SecAppDomainApprovalStatus(appID
, fqdn
, error
);
2551 // ignore interim error since we have multiple domains to check
2552 CFReleaseNull(*error
);
2554 bool approved
= (flags
& kSWCFlag_SiteApproved
);
2557 CFArrayAppendArray(foundItems
, items
, CFRangeMake(0, CFArrayGetCount(items
)));
2560 CFReleaseSafe(items
);
2561 CFReleaseSafe(attrs
);
2562 CFReleaseSafe(fqdn
);
2565 // If matching credentials are found, the credentials provided to the completionHandler
2566 // will be a CFArrayRef containing CFDictionaryRef entries. Each dictionary entry will
2567 // contain the following pairs (see Security/SecItem.h):
2568 // key: kSecAttrServer value: CFStringRef (the website)
2569 // key: kSecAttrAccount value: CFStringRef (the account)
2570 // key: kSecSharedPassword value: CFStringRef (the password)
2572 // key: kSecAttrPort value: CFNumberRef (the port number, if non-standard for https)
2574 count
= CFArrayGetCount(foundItems
);
2575 for (idx
= 0; idx
< count
; idx
++) {
2576 CFDictionaryRef dict
= (CFDictionaryRef
) CFArrayGetValueAtIndex(foundItems
, idx
);
2577 CFMutableDictionaryRef newdict
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2578 if (newdict
&& dict
&& CFGetTypeID(dict
) == CFDictionaryGetTypeID()) {
2579 CFStringRef srvr
= CFDictionaryGetValue(dict
, kSecAttrServer
);
2580 CFStringRef acct
= CFDictionaryGetValue(dict
, kSecAttrAccount
);
2581 CFNumberRef pnum
= CFDictionaryGetValue(dict
, kSecAttrPort
);
2582 CFStringRef icmt
= CFDictionaryGetValue(dict
, kSecAttrComment
);
2583 CFDataRef data
= CFDictionaryGetValue(dict
, kSecValueData
);
2585 CFDictionaryAddValue(newdict
, kSecAttrServer
, srvr
);
2588 CFDictionaryAddValue(newdict
, kSecAttrAccount
, acct
);
2592 if (CFNumberGetValue(pnum
, kCFNumberSInt16Type
, &pval
) &&
2593 (pval
< -1 || pval
> 0)) {
2594 CFDictionaryAddValue(newdict
, kSecAttrPort
, pnum
);
2598 CFStringRef password
= CFStringCreateFromExternalRepresentation(kCFAllocatorDefault
, data
, kCFStringEncodingUTF8
);
2600 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH && !TARGET_OS_TV
2601 CFDictionaryAddValue(newdict
, kSecSharedPassword
, password
);
2603 CFDictionaryAddValue(newdict
, CFSTR("spwd"), password
);
2605 CFReleaseSafe(password
);
2608 if (icmt
&& CFEqual(icmt
, kSecSafariDefaultComment
)) {
2609 CFArrayInsertValueAtIndex(credentials
, 0, newdict
);
2611 CFArrayAppendValue(credentials
, newdict
);
2614 CFReleaseSafe(newdict
);
2621 // create a new array of dictionaries (without the actual password) for picker UI
2622 count
= CFArrayGetCount(credentials
);
2623 CFMutableArrayRef items
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
2624 for (idx
= 0; idx
< count
; idx
++) {
2625 CFDictionaryRef dict
= (CFDictionaryRef
) CFArrayGetValueAtIndex(credentials
, idx
);
2626 CFMutableDictionaryRef newdict
= CFDictionaryCreateMutableCopy(kCFAllocatorDefault
, 0, dict
);
2627 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH && !TARGET_OS_TV
2628 CFDictionaryRemoveValue(newdict
, kSecSharedPassword
);
2630 CFDictionaryRemoveValue(newdict
, CFSTR("spwd"));
2632 CFArrayAppendValue(items
, newdict
);
2633 CFReleaseSafe(newdict
);
2636 // prompt user to select one of the dictionary items
2637 CFDictionaryRef selected
= swca_copy_selected_dictionary(swca_select_request_id
,
2638 clientAuditToken
, items
, error
);
2640 // find the matching item in our credentials array
2641 CFStringRef srvr
= CFDictionaryGetValue(selected
, kSecAttrServer
);
2642 CFStringRef acct
= CFDictionaryGetValue(selected
, kSecAttrAccount
);
2643 CFNumberRef pnum
= CFDictionaryGetValue(selected
, kSecAttrPort
);
2644 for (idx
= 0; idx
< count
; idx
++) {
2645 CFDictionaryRef dict
= (CFDictionaryRef
) CFArrayGetValueAtIndex(credentials
, idx
);
2646 CFStringRef srvr1
= CFDictionaryGetValue(dict
, kSecAttrServer
);
2647 CFStringRef acct1
= CFDictionaryGetValue(dict
, kSecAttrAccount
);
2648 CFNumberRef pnum1
= CFDictionaryGetValue(dict
, kSecAttrPort
);
2650 if (!srvr
|| !srvr1
|| !CFEqual(srvr
, srvr1
)) continue;
2651 if (!acct
|| !acct1
|| !CFEqual(acct
, acct1
)) continue;
2652 if ((pnum
&& pnum1
) && !CFEqual(pnum
, pnum1
)) continue;
2655 CFReleaseSafe(selected
);
2662 CFReleaseSafe(items
);
2663 CFArrayRemoveAllValues(credentials
);
2664 if (selected
&& ok
) {
2665 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
2666 fqdn
= CFDictionaryGetValue(selected
, kSecAttrServer
);
2668 CFArrayAppendValue(credentials
, selected
);
2672 #if TARGET_OS_IOS && !TARGET_OS_BRIDGE && !TARGET_IPHONE_SIMULATOR
2673 // register confirmation with database
2674 CFRetainSafe(appID
);
2676 if (0 != SWCSetServiceFlags(kSecSharedWebCredentialsService
,
2677 appID
, fqdn
, kSWCFlag_ExternalMask
, kSWCFlag_UserApproved
,
2678 ^void(OSStatus inStatus
, SWCFlags inNewFlags
){
2679 CFReleaseSafe(appID
);
2680 CFReleaseSafe(fqdn
);
2683 // we didn't queue the block
2684 CFReleaseSafe(appID
);
2685 CFReleaseSafe(fqdn
);
2689 CFReleaseSafe(selected
);
2691 else if (NULL
== *error
) {
2692 // found no items, and we haven't already filled in the error
2693 SecError(errSecItemNotFound
, error
, CFSTR("no matching items found"));
2698 CFArrayRemoveAllValues(credentials
);
2699 CFReleaseNull(credentials
);
2701 CFReleaseSafe(foundItems
);
2702 *result
= credentials
;
2703 CFReleaseSafe(swcclient
.accessGroups
);
2704 CFReleaseSafe(fqdns
);
2709 #endif /* TARGET_OS_IOS */
2713 // MARK: Keychain backup
2715 CF_RETURNS_RETAINED CFDataRef
2716 _SecServerKeychainCreateBackup(SecurityClient
*client
, CFDataRef keybag
, CFDataRef passcode
, CFErrorRef
*error
) {
2717 __block CFDataRef backup
;
2718 kc_with_dbt(true, error
, ^bool (SecDbConnectionRef dbt
) {
2722 if (keybag
== NULL
&& passcode
== NULL
) {
2724 backup
= SecServerExportBackupableKeychain(dbt
, client
, KEYBAG_DEVICE
, backup_keybag_handle
, error
);
2725 #else /* !USE_KEYSTORE */
2727 SecError(errSecParam
, error
, CFSTR("Why are you doing this?"));
2729 #endif /* USE_KEYSTORE */
2731 backup
= SecServerKeychainCreateBackup(dbt
, client
, keybag
, passcode
, error
);
2733 return (backup
!= NULL
);
2740 _SecServerKeychainRestore(CFDataRef backup
, SecurityClient
*client
, CFDataRef keybag
, CFDataRef passcode
, CFErrorRef
*error
) {
2741 if (backup
== NULL
|| keybag
== NULL
)
2742 return SecError(errSecParam
, error
, CFSTR("backup or keybag missing"));
2744 __block
bool ok
= true;
2745 ok
&= kc_with_dbt(true, error
, ^bool (SecDbConnectionRef dbconn
) {
2746 return SecServerKeychainRestore(dbconn
, client
, backup
, keybag
, passcode
, error
);
2750 SecKeychainChanged();
2757 _SecServerBackupCopyUUID(CFDataRef data
, CFErrorRef
*error
)
2759 CFStringRef uuid
= NULL
;
2760 CFDictionaryRef backup
;
2762 backup
= CFPropertyListCreateWithData(kCFAllocatorDefault
, data
,
2763 kCFPropertyListImmutable
, NULL
,
2765 if (isDictionary(backup
)) {
2766 uuid
= SecServerBackupGetKeybagUUID(backup
, error
);
2770 CFReleaseNull(backup
);
2778 // MARK: SecItemDataSource
2780 // Make sure to call this before any writes to the keychain, so that we fire
2781 // up the engines to monitor manifest changes.
2782 SOSDataSourceFactoryRef
SecItemDataSourceFactoryGetDefault(void) {
2783 return SecItemDataSourceFactoryGetShared(kc_dbhandle(NULL
));
2786 /* AUDIT[securityd]:
2787 args_in (ok) is a caller provided, CFDictionaryRef.
2790 CF_RETURNS_RETAINED CFArrayRef
2791 _SecServerKeychainSyncUpdateMessage(CFDictionaryRef updates
, CFErrorRef
*error
) {
2792 // This never fails, trust us!
2793 return SOSCCHandleUpdateMessage(updates
);
2797 // Truthiness in the cloud backup/restore support.
2800 static CFDictionaryRef
2801 _SecServerCopyTruthInTheCloud(CFDataRef keybag
, CFDataRef password
,
2802 CFDictionaryRef backup
, CFErrorRef
*error
)
2804 SOSManifestRef mold
= NULL
, mnow
= NULL
, mdelete
= NULL
, madd
= NULL
;
2805 __block CFMutableDictionaryRef backup_new
= NULL
;
2806 keybag_handle_t bag_handle
;
2807 if (!ks_open_keybag(keybag
, password
, &bag_handle
, error
))
2810 // We need to have a datasource singleton for protection domain
2811 // kSecAttrAccessibleWhenUnlocked and keep a single shared engine
2812 // instance around which we create in the datasource constructor as well.
2813 SOSDataSourceFactoryRef dsf
= SecItemDataSourceFactoryGetDefault();
2814 SOSDataSourceRef ds
= SOSDataSourceFactoryCreateDataSource(dsf
, kSecAttrAccessibleWhenUnlocked
, error
);
2816 backup_new
= backup
? CFDictionaryCreateMutableCopy(kCFAllocatorDefault
, 0, backup
) : CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2817 mold
= SOSCreateManifestWithBackup(backup
, error
);
2818 SOSEngineRef engine
= SOSDataSourceGetSharedEngine(ds
, error
);
2819 mnow
= SOSEngineCopyManifest(engine
, NULL
);
2821 mnow
= SOSDataSourceCopyManifestWithViewNameSet(ds
, SOSViewsGetV0ViewSet(), error
);
2824 CFReleaseNull(backup_new
);
2825 secerror("failed to obtain manifest for keychain: %@", error
? *error
: NULL
);
2827 SOSManifestDiff(mold
, mnow
, &mdelete
, &madd
, error
);
2830 // Delete everything from the new_backup that is no longer in the datasource according to the datasources manifest.
2831 SOSManifestForEach(mdelete
, ^(CFDataRef digest_data
, bool *stop
) {
2832 CFStringRef deleted_item_key
= CFDataCopyHexString(digest_data
);
2833 CFDictionaryRemoveValue(backup_new
, deleted_item_key
);
2834 CFRelease(deleted_item_key
);
2837 CFMutableArrayRef changes
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
2838 SOSDataSourceForEachObject(ds
, NULL
, madd
, error
, ^void(CFDataRef digest
, SOSObjectRef object
, bool *stop
) {
2839 CFErrorRef localError
= NULL
;
2840 CFDataRef digest_data
= NULL
;
2841 CFTypeRef value
= NULL
;
2843 // Key in our manifest can't be found in db, remove it from our manifest
2844 SOSChangesAppendDelete(changes
, digest
);
2845 } else if (!(digest_data
= SOSObjectCopyDigest(ds
, object
, &localError
))
2846 || !(value
= SOSObjectCopyBackup(ds
, object
, bag_handle
, &localError
))) {
2847 if (SecErrorGetOSStatus(localError
) == errSecDecode
) {
2848 // Ignore decode errors, pretend the objects aren't there
2849 CFRelease(localError
);
2850 // Object undecodable, remove it from our manifest
2851 SOSChangesAppendDelete(changes
, digest
);
2853 // Stop iterating and propagate out all other errors.
2855 *error
= localError
;
2856 CFReleaseNull(backup_new
);
2859 // TODO: Should we skip tombstones here?
2860 CFStringRef key
= CFDataCopyHexString(digest_data
);
2861 CFDictionarySetValue(backup_new
, key
, value
);
2864 CFReleaseSafe(digest_data
);
2865 CFReleaseSafe(value
);
2866 }) || CFReleaseNull(backup_new
);
2868 if (CFArrayGetCount(changes
)) {
2869 if (!SOSEngineUpdateChanges(engine
, kSOSDataSourceSOSTransaction
, changes
, error
)) {
2870 CFReleaseNull(backup_new
);
2873 CFReleaseSafe(changes
);
2875 SOSDataSourceRelease(ds
, error
) || CFReleaseNull(backup_new
);
2878 CFReleaseSafe(mold
);
2879 CFReleaseSafe(mnow
);
2880 CFReleaseSafe(madd
);
2881 CFReleaseSafe(mdelete
);
2882 ks_close_keybag(bag_handle
, error
) || CFReleaseNull(backup_new
);
2888 _SecServerRestoreTruthInTheCloud(CFDataRef keybag
, CFDataRef password
, CFDictionaryRef backup_in
, CFErrorRef
*error
) {
2889 __block
bool ok
= true;
2890 keybag_handle_t bag_handle
;
2891 if (!ks_open_keybag(keybag
, password
, &bag_handle
, error
))
2894 SOSManifestRef mbackup
= SOSCreateManifestWithBackup(backup_in
, error
);
2896 SOSDataSourceFactoryRef dsf
= SecItemDataSourceFactoryGetDefault();
2897 SOSDataSourceRef ds
= SOSDataSourceFactoryCreateDataSource(dsf
, kSecAttrAccessibleWhenUnlocked
, error
);
2898 ok
&= ds
&& SOSDataSourceWith(ds
, error
, ^(SOSTransactionRef txn
, bool *commit
) {
2899 SOSManifestRef mnow
= SOSDataSourceCopyManifestWithViewNameSet(ds
, SOSViewsGetV0BackupViewSet(), error
);
2900 SOSManifestRef mdelete
= NULL
, madd
= NULL
;
2901 SOSManifestDiff(mnow
, mbackup
, &mdelete
, &madd
, error
);
2903 // Don't delete everything in datasource not in backup.
2905 // Add items from the backup
2906 SOSManifestForEach(madd
, ^void(CFDataRef e
, bool *stop
) {
2907 CFDictionaryRef item
= NULL
;
2908 CFStringRef sha1
= CFDataCopyHexString(e
);
2910 item
= CFDictionaryGetValue(backup_in
, sha1
);
2914 CFErrorRef localError
= NULL
;
2916 if (!SOSObjectRestoreObject(ds
, txn
, bag_handle
, item
, &localError
)) {
2917 OSStatus status
= SecErrorGetOSStatus(localError
);
2918 if (status
== errSecDuplicateItem
) {
2919 // Log and ignore duplicate item errors during restore
2920 secnotice("titc", "restore %@ not replacing existing item", item
);
2921 } else if (status
== errSecDecode
) {
2922 // Log and ignore corrupted item errors during restore
2923 secnotice("titc", "restore %@ skipping corrupted item %@", item
, localError
);
2925 if (status
== errSecInteractionNotAllowed
)
2927 // Propagate the first other error upwards (causing the restore to fail).
2928 secerror("restore %@ failed %@", item
, localError
);
2930 if (error
&& !*error
) {
2931 *error
= localError
;
2935 CFReleaseSafe(localError
);
2939 ok
&= SOSDataSourceRelease(ds
, error
);
2940 CFReleaseNull(mdelete
);
2941 CFReleaseNull(madd
);
2942 CFReleaseNull(mnow
);
2947 ok
&= ks_close_keybag(bag_handle
, error
);
2953 CF_RETURNS_RETAINED CFDictionaryRef
2954 _SecServerBackupSyncable(CFDictionaryRef backup
, CFDataRef keybag
, CFDataRef password
, CFErrorRef
*error
) {
2955 require_action_quiet(isData(keybag
), errOut
, SecError(errSecParam
, error
, CFSTR("keybag %@ not a data"), keybag
));
2956 require_action_quiet(!backup
|| isDictionary(backup
), errOut
, SecError(errSecParam
, error
, CFSTR("backup %@ not a dictionary"), backup
));
2957 require_action_quiet(!password
|| isData(password
), errOut
, SecError(errSecParam
, error
, CFSTR("password %@ not a data"), password
));
2959 return _SecServerCopyTruthInTheCloud(keybag
, password
, backup
, error
);
2966 _SecServerRestoreSyncable(CFDictionaryRef backup
, CFDataRef keybag
, CFDataRef password
, CFErrorRef
*error
) {
2968 require_action_quiet(isData(keybag
), errOut
, ok
= SecError(errSecParam
, error
, CFSTR("keybag %@ not a data"), keybag
));
2969 require_action_quiet(isDictionary(backup
), errOut
, ok
= SecError(errSecParam
, error
, CFSTR("backup %@ not a dictionary"), backup
));
2972 require_action_quiet(isData(password
), errOut
, ok
= SecError(errSecParam
, error
, CFSTR("password not a data")));
2975 ok
= _SecServerRestoreTruthInTheCloud(keybag
, password
, backup
, error
);
2981 bool _SecServerRollKeysGlue(bool force
, CFErrorRef
*error
) {
2982 return _SecServerRollKeys(force
, NULL
, error
);
2986 bool _SecServerRollKeys(bool force
, SecurityClient
*client
, CFErrorRef
*error
) {
2988 uint32_t keystore_generation_status
= 0;
2989 if (aks_generation(KEYBAG_DEVICE
, generation_noop
, &keystore_generation_status
))
2991 uint32_t current_generation
= keystore_generation_status
& generation_current
;
2993 return kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
2994 bool up_to_date
= s3dl_dbt_keys_current(dbt
, current_generation
, NULL
);
2996 if (force
&& !up_to_date
) {
2997 up_to_date
= s3dl_dbt_update_keys(dbt
, client
, error
);
2999 secerror("Completed roll keys.");
3000 up_to_date
= s3dl_dbt_keys_current(dbt
, current_generation
, NULL
);
3003 secerror("Failed to roll keys.");
3013 InitialSyncItems(CFMutableArrayRef items
, bool limitToCurrent
, CFStringRef agrp
, const SecDbClass
*qclass
, CFErrorRef
*error
)
3015 bool result
= false;
3018 q
= query_create(qclass
, NULL
, NULL
, error
);
3021 q
->q_return_type
= kSecReturnDataMask
| kSecReturnAttributesMask
;
3022 q
->q_limit
= kSecMatchUnlimited
;
3023 q
->q_keybag
= KEYBAG_DEVICE
;
3025 query_add_attribute(kSecAttrAccessGroup
, agrp
, q
);
3026 query_add_attribute(kSecAttrSynchronizable
, kCFBooleanTrue
, q
);
3027 query_add_attribute(kSecAttrTombstone
, kCFBooleanFalse
, q
);
3029 result
= kc_with_dbt(false, error
, ^(SecDbConnectionRef dbt
) {
3030 return kc_transaction(dbt
, error
, ^{
3031 CFErrorRef error2
= NULL
;
3033 SecDbItemSelect(q
, dbt
, &error2
, NULL
, ^bool(const SecDbAttr
*attr
) {
3034 return CFDictionaryGetValue(q
->q_item
, attr
->name
);
3035 }, NULL
, NULL
, ^(SecDbItemRef item
, bool *stop
) {
3036 CFErrorRef error3
= NULL
;
3037 secinfo("InitialSyncItems", "Copy item");
3039 CFMutableDictionaryRef attrs
= SecDbItemCopyPListWithMask(item
, kSecDbSyncFlag
, &error3
);
3042 CFStringRef vwht
= CFDictionaryGetValue(attrs
, kSecAttrSyncViewHint
);
3044 * Saying its a SOS viewhint is really not the right answer post Triangle
3046 if (isString(vwht
) && !SOSViewInSOSSystem(vwht
)) {
3050 * Here we encode how PCS stores identities so that we only copy the
3051 * current identites for performance reasons.
3053 if (limitToCurrent
) {
3054 enum { PCS_CURRENT_IDENTITY_OFFSET
= 0x10000 };
3057 CFNumberRef type
= CFDictionaryGetValue(attrs
, kSecAttrType
);
3058 if (!isNumber(type
)) {
3059 // still allow this case since its not a service identity ??
3060 } else if (!CFNumberGetValue(type
, kCFNumberSInt32Type
, &s32
)) {
3062 } else if ((s32
& PCS_CURRENT_IDENTITY_OFFSET
) == 0) {
3067 CFDictionaryAddValue(attrs
, kSecClass
, SecDbItemGetClass(item
)->name
);
3068 CFArrayAppendValue(items
, attrs
);
3071 CFReleaseNull(attrs
);
3073 CFReleaseNull(error3
);
3075 CFReleaseNull(error2
);
3083 query_destroy(q
, NULL
);
3088 _SecServerCopyInitialSyncCredentials(uint32_t flags
, CFErrorRef
*error
)
3090 CFMutableArrayRef items
= CFArrayCreateMutableForCFTypes(NULL
);
3092 if (flags
& SecServerInitialSyncCredentialFlagTLK
) {
3093 require_action(InitialSyncItems(items
, false, CFSTR("com.apple.security.ckks"), inet_class(), error
), fail
,
3094 secerror("failed to collect PCS-inet keys: %@", error
? *error
: NULL
));
3096 if (flags
& SecServerInitialSyncCredentialFlagPCS
) {
3097 bool onlyCurrent
= !(flags
& SecServerInitialSyncCredentialFlagPCSNonCurrent
);
3099 require_action(InitialSyncItems(items
, false, CFSTR("com.apple.ProtectedCloudStorage"), genp_class(), error
), fail
,
3100 secerror("failed to collect PCS-inet keys: %@", error
? *error
: NULL
));
3101 require_action(InitialSyncItems(items
, onlyCurrent
, CFSTR("com.apple.ProtectedCloudStorage"), inet_class(), error
), fail
,
3102 secerror("failed to collect PCS-inet keys: %@", error
? *error
: NULL
));
3110 _SecServerImportInitialSyncCredentials(CFArrayRef array
, CFErrorRef
*error
)
3112 return kc_with_dbt(true, error
, ^bool(SecDbConnectionRef dbt
) {
3113 return kc_transaction(dbt
, error
, ^bool(void){
3114 CFIndex n
, count
= CFArrayGetCount(array
);
3116 secinfo("ImportInitialSyncItems", "Importing %d items", (int)count
);
3118 for (n
= 0; n
< count
; n
++) {
3119 CFErrorRef cferror
= NULL
;
3121 CFDictionaryRef item
= CFArrayGetValueAtIndex(array
, n
);
3122 if (!isDictionary(item
))
3125 CFStringRef className
= CFDictionaryGetValue(item
, kSecClass
);
3126 if (className
== NULL
) {
3127 secinfo("ImportInitialSyncItems", "Item w/o class");
3131 const SecDbClass
*cls
= kc_class_with_name(className
);
3133 secinfo("ImportInitialSyncItems", "Item with unknown class: %@", className
);
3137 SecDbItemRef dbi
= SecDbItemCreateWithAttributes(NULL
, cls
, item
, KEYBAG_DEVICE
, &cferror
);
3139 secinfo("ImportInitialSyncItems", "Item creation failed with: %@", cferror
);
3140 CFReleaseNull(cferror
);
3144 if (!SecDbItemSetSyncable(dbi
, true, &cferror
)) {
3145 secinfo("ImportInitialSyncItems", "Failed to set sync=1: %@ for item %@", cferror
, dbi
);
3146 CFReleaseNull(cferror
);
3151 if (!SecDbItemInsert(dbi
, dbt
, &cferror
)) {
3152 secinfo("ImportInitialSyncItems", "Item store failed with: %@: %@", cferror
, dbi
);
3153 CFReleaseNull(cferror
);
3167 * Sync bubble migration code
3170 struct SyncBubbleRule
{
3171 CFStringRef attribute
;
3176 TransmogrifyItemsToSyncBubble(SecurityClient
*client
, uid_t uid
,
3179 const SecDbClass
*qclass
,
3180 struct SyncBubbleRule
*items
, CFIndex nItems
,
3183 CFMutableDictionaryRef updateAttributes
= NULL
;
3184 CFDataRef syncBubbleView
= NULL
;
3185 CFDataRef activeUserView
= NULL
;
3190 syncBubbleView
= SecMUSRCreateSyncBubbleUserUUID(uid
);
3191 require(syncBubbleView
, fail
);
3193 activeUserView
= SecMUSRCreateActiveUserUUID(uid
);
3194 require(activeUserView
, fail
);
3197 if ((onlyDelete
&& !copyToo
) || !onlyDelete
) {
3200 * Clean out items first
3203 secnotice("syncbubble", "cleaning out old items");
3205 q
= query_create(qclass
, NULL
, NULL
, error
);
3208 q
->q_limit
= kSecMatchUnlimited
;
3209 q
->q_keybag
= device_keybag_handle
;
3211 for (n
= 0; n
< nItems
; n
++) {
3212 query_add_attribute(items
[n
].attribute
, items
[n
].value
, q
);
3214 q
->q_musrView
= CFRetain(syncBubbleView
);
3215 require(q
->q_musrView
, fail
);
3217 kc_with_dbt(false, error
, ^(SecDbConnectionRef dbt
) {
3218 return kc_transaction(dbt
, error
, ^{
3219 return s3dl_query_delete(dbt
, q
, NULL
, error
);
3223 query_destroy(q
, NULL
);
3228 if (onlyDelete
|| !copyToo
) {
3229 secnotice("syncbubble", "skip migration of items");
3232 * Copy over items from EMCS to sync bubble
3235 secnotice("syncbubble", "migrating sync bubble items");
3237 q
= query_create(qclass
, NULL
, NULL
, error
);
3240 q
->q_return_type
= kSecReturnDataMask
| kSecReturnAttributesMask
;
3241 q
->q_limit
= kSecMatchUnlimited
;
3242 q
->q_keybag
= device_keybag_handle
; /* XXX change to session key bag when it exists */
3244 for (n
= 0; n
< nItems
; n
++) {
3245 query_add_or_attribute(items
[n
].attribute
, items
[n
].value
, q
);
3247 query_add_or_attribute(CFSTR("musr"), activeUserView
, q
);
3248 q
->q_musrView
= CFRetain(activeUserView
);
3250 updateAttributes
= CFDictionaryCreateMutableForCFTypes(NULL
);
3251 require(updateAttributes
, fail
);
3253 CFDictionarySetValue(updateAttributes
, CFSTR("musr"), syncBubbleView
); /* XXX should use kSecAttrMultiUser */
3256 kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
3257 return kc_transaction(dbt
, error
, ^{
3258 CFErrorRef error2
= NULL
;
3260 SecDbItemSelect(q
, dbt
, &error2
, NULL
, ^bool(const SecDbAttr
*attr
) {
3261 return CFDictionaryGetValue(q
->q_item
, attr
->name
);
3262 }, NULL
, NULL
, ^(SecDbItemRef item
, bool *stop
) {
3263 CFErrorRef error3
= NULL
;
3264 secinfo("syncbubble", "migrating item");
3266 SecDbItemRef new_item
= SecDbItemCopyWithUpdates(item
, updateAttributes
, NULL
);
3267 if (new_item
== NULL
)
3270 SecDbItemClearRowId(new_item
, NULL
);
3272 if (!SecDbItemSetKeybag(new_item
, device_keybag_handle
, NULL
)) {
3273 CFRelease(new_item
);
3277 if (!SecDbItemInsert(new_item
, dbt
, &error3
)) {
3278 secnotice("syncbubble", "migration failed with %@ for item %@", error3
, new_item
);
3280 CFRelease(new_item
);
3281 CFReleaseNull(error3
);
3283 CFReleaseNull(error2
);
3292 CFReleaseNull(syncBubbleView
);
3293 CFReleaseNull(activeUserView
);
3294 CFReleaseNull(updateAttributes
);
3296 query_destroy(q
, NULL
);
3301 static struct SyncBubbleRule PCSItems
[] = {
3303 .attribute
= CFSTR("agrp"),
3304 .value
= CFSTR("com.apple.ProtectedCloudStorage"),
3307 static struct SyncBubbleRule NSURLSesssiond
[] = {
3309 .attribute
= CFSTR("agrp"),
3310 .value
= CFSTR("com.apple.nsurlsessiond"),
3313 static struct SyncBubbleRule AccountsdItems
[] = {
3315 .attribute
= CFSTR("svce"),
3316 .value
= CFSTR("com.apple.account.AppleAccount.token"),
3319 .attribute
= CFSTR("svce"),
3320 .value
= CFSTR("com.apple.account.AppleAccount.password"),
3323 .attribute
= CFSTR("svce"),
3324 .value
= CFSTR("com.apple.account.AppleAccount.rpassword"),
3327 .attribute
= CFSTR("svce"),
3328 .value
= CFSTR("com.apple.account.idms.token"),
3331 .attribute
= CFSTR("svce"),
3332 .value
= CFSTR("com.apple.account.idms.continuation-key"),
3335 .attribute
= CFSTR("svce"),
3336 .value
= CFSTR("com.apple.account.CloudKit.token"),
3340 static struct SyncBubbleRule MobileMailItems
[] = {
3342 .attribute
= CFSTR("svce"),
3343 .value
= CFSTR("com.apple.account.IMAP.password"),
3346 .attribute
= CFSTR("svce"),
3347 .value
= CFSTR("com.apple.account.SMTP.password"),
3350 .attribute
= CFSTR("svce"),
3351 .value
= CFSTR("com.apple.account.Exchange.password"),
3354 .attribute
= CFSTR("svce"),
3355 .value
= CFSTR("com.apple.account.Hotmail.password"),
3358 .attribute
= CFSTR("svce"),
3359 .value
= CFSTR("com.apple.account.Google.password"),
3362 .attribute
= CFSTR("svce"),
3363 .value
= CFSTR("com.apple.account.Google.oauth-token"),
3366 .attribute
= CFSTR("svce"),
3367 .value
= CFSTR("com.apple.account.Google.oath-refresh-token"),
3370 .attribute
= CFSTR("svce"),
3371 .value
= CFSTR("com.apple.account.Yahoo.password"),
3374 .attribute
= CFSTR("svce"),
3375 .value
= CFSTR("com.apple.account.Yahoo.oauth-token"),
3378 .attribute
= CFSTR("svce"),
3379 .value
= CFSTR("com.apple.account.Yahoo.oauth-token-nosync"),
3382 .attribute
= CFSTR("svce"),
3383 .value
= CFSTR("com.apple.account.Yahoo.oath-refresh-token"),
3386 .attribute
= CFSTR("svce"),
3387 .value
= CFSTR("com.apple.account.IMAPNotes.password"),
3390 .attribute
= CFSTR("svce"),
3391 .value
= CFSTR("com.apple.account.IMAPMail.password"),
3394 .attribute
= CFSTR("svce"),
3395 .value
= CFSTR("com.apple.account.126.password"),
3398 .attribute
= CFSTR("svce"),
3399 .value
= CFSTR("com.apple.account.163.password"),
3402 .attribute
= CFSTR("svce"),
3403 .value
= CFSTR("com.apple.account.aol.password"),
3408 ArrayContains(CFArrayRef array
, CFStringRef service
)
3410 return CFArrayContainsValue(array
, CFRangeMake(0, CFArrayGetCount(array
)), service
);
3414 _SecServerTransmogrifyToSyncBubble(CFArrayRef services
, uid_t uid
, SecurityClient
*client
, CFErrorRef
*error
)
3416 bool copyCloudAuthToken
= false;
3417 bool copyMobileMail
= false;
3419 bool copyPCS
= false;
3420 bool onlyDelete
= false;
3421 bool copyNSURLSesssion
= false;
3423 if (!client
->inMultiUser
)
3426 secnotice("syncbubble", "migration for uid %d uid for services %@", (int)uid
, services
);
3428 #if TARGET_OS_SIMULATOR
3431 if (uid
!= (uid_t
)client
->activeUser
)
3434 #error "no sync bubble on other platforms"
3438 * First select that services to copy/delete
3441 if (ArrayContains(services
, CFSTR("com.apple.bird.usermanager.sync"))
3442 || ArrayContains(services
, CFSTR("com.apple.cloudphotod.sync"))
3443 || ArrayContains(services
, CFSTR("com.apple.cloudphotod.syncstakeholder"))
3444 || ArrayContains(services
, CFSTR("com.apple.cloudd.usermanager.sync")))
3446 copyCloudAuthToken
= true;
3450 if (ArrayContains(services
, CFSTR("com.apple.nsurlsessiond.usermanager.sync")))
3452 copyCloudAuthToken
= true;
3453 copyNSURLSesssion
= true;
3456 if (ArrayContains(services
, CFSTR("com.apple.syncdefaultsd.usermanager.sync"))) {
3457 copyCloudAuthToken
= true;
3459 if (ArrayContains(services
, CFSTR("com.apple.mailq.sync")) || ArrayContains(services
, CFSTR("com.apple.mailq.sync.xpc"))) {
3460 copyCloudAuthToken
= true;
3461 copyMobileMail
= true;
3466 * The actually copy/delete the items selected
3469 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyPCS
, inet_class(), PCSItems
, sizeof(PCSItems
)/sizeof(PCSItems
[0]), error
);
3471 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyPCS
, genp_class(), PCSItems
, sizeof(PCSItems
)/sizeof(PCSItems
[0]), error
);
3475 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyMobileMail
, genp_class(), MobileMailItems
, sizeof(MobileMailItems
)/sizeof(MobileMailItems
[0]), error
);
3479 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyCloudAuthToken
, genp_class(), AccountsdItems
, sizeof(AccountsdItems
)/sizeof(AccountsdItems
[0]), error
);
3483 res
= TransmogrifyItemsToSyncBubble(client
, uid
, onlyDelete
, copyNSURLSesssion
, inet_class(), NSURLSesssiond
, sizeof(NSURLSesssiond
)/sizeof(NSURLSesssiond
[0]), error
);
3491 * Migrate from user keychain to system keychain when switching to edu mode
3495 _SecServerTransmogrifyToSystemKeychain(SecurityClient
*client
, CFErrorRef
*error
)
3497 __block
bool ok
= true;
3500 * we are not in multi user yet, about to switch, otherwise we would
3501 * check that for client->inMultiuser here
3504 kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
3505 return kc_transaction(dbt
, error
, ^{
3506 CFDataRef systemUUID
= SecMUSRGetSystemKeychainUUID();
3508 const SecDbSchema
*newSchema
= current_schema();
3509 SecDbClass
const *const *kcClass
;
3511 for (kcClass
= newSchema
->classes
; *kcClass
!= NULL
; kcClass
++) {
3512 CFErrorRef localError
= NULL
;
3515 if (!((*kcClass
)->itemclass
)) {
3519 q
= query_create(*kcClass
, SecMUSRGetSingleUserKeychainUUID(), NULL
, error
);
3523 ok
&= SecDbItemSelect(q
, dbt
, error
, ^bool(const SecDbAttr
*attr
) {
3524 return (attr
->flags
& kSecDbInFlag
) != 0;
3525 }, ^bool(const SecDbAttr
*attr
) {
3526 // No filtering please.
3528 }, ^bool(CFMutableStringRef sql
, bool *needWhere
) {
3529 SecDbAppendWhereOrAnd(sql
, needWhere
);
3530 CFStringAppendFormat(sql
, NULL
, CFSTR("musr = ?"));
3532 }, ^bool(sqlite3_stmt
*stmt
, int col
) {
3533 return SecDbBindObject(stmt
, col
++, SecMUSRGetSingleUserKeychainUUID(), error
);
3534 }, ^(SecDbItemRef item
, bool *stop
) {
3535 CFErrorRef localError
= NULL
;
3537 if (!SecDbItemSetValueWithName(item
, kSecAttrMultiUser
, systemUUID
, &localError
)) {
3538 secerror("item: %@ update musr to system failed: %@", item
, localError
);
3543 if (!SecDbItemDoUpdate(item
, item
, dbt
, &localError
, ^bool (const SecDbAttr
*attr
) {
3544 return attr
->kind
== kSecDbRowIdAttr
;
3546 secerror("item: %@ insert during UPDATE: %@", item
, localError
);
3552 SecErrorPropagate(localError
, error
);
3553 CFReleaseSafe(localError
);
3557 query_destroy(q
, &localError
);
3568 * Delete account from local usage
3572 _SecServerDeleteMUSERViews(SecurityClient
*client
, uid_t uid
, CFErrorRef
*error
)
3574 return kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
3575 CFDataRef musrView
= NULL
, syncBubbleView
= NULL
;
3578 syncBubbleView
= SecMUSRCreateSyncBubbleUserUUID(uid
);
3579 require(syncBubbleView
, fail
);
3581 musrView
= SecMUSRCreateActiveUserUUID(uid
);
3582 require(musrView
, fail
);
3584 require(ok
= SecServerDeleteAllForUser(dbt
, syncBubbleView
, false, error
), fail
);
3585 require(ok
= SecServerDeleteAllForUser(dbt
, musrView
, false, error
), fail
);
3588 CFReleaseNull(syncBubbleView
);
3589 CFReleaseNull(musrView
);
3595 #endif /* TARGET_OS_IOS */
3598 _SecServerGetKeyStats(const SecDbClass
*qclass
,
3599 struct _SecServerKeyStats
*stats
)
3601 __block CFErrorRef error
= NULL
;
3604 Query
*q
= query_create(qclass
, NULL
, NULL
, &error
);
3607 q
->q_return_type
= kSecReturnDataMask
| kSecReturnAttributesMask
;
3608 q
->q_limit
= kSecMatchUnlimited
;
3609 q
->q_keybag
= KEYBAG_DEVICE
;
3610 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleWhenUnlocked
, q
);
3611 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleAfterFirstUnlock
, q
);
3612 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleAlways
, q
);
3613 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleWhenUnlockedThisDeviceOnly
, q
);
3614 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
, q
);
3615 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleAlwaysThisDeviceOnly
, q
);
3616 query_add_attribute(kSecAttrTombstone
, kCFBooleanFalse
, q
);
3618 kc_with_dbt(false, &error
, ^(SecDbConnectionRef dbconn
) {
3619 CFErrorRef error2
= NULL
;
3620 __block CFIndex totalSize
= 0;
3621 stats
->maxDataSize
= 0;
3623 SecDbItemSelect(q
, dbconn
, &error2
, NULL
, ^bool(const SecDbAttr
*attr
) {
3624 return CFDictionaryContainsKey(q
->q_item
, attr
->name
);
3625 }, NULL
, NULL
, ^(SecDbItemRef item
, bool *stop
) {
3626 CFErrorRef error3
= NULL
;
3627 CFDataRef data
= SecDbItemGetValue(item
, &v6v_Data
, &error3
);
3629 CFIndex size
= CFDataGetLength(data
);
3630 if (size
> stats
->maxDataSize
)
3631 stats
->maxDataSize
= size
;
3635 CFReleaseNull(error3
);
3637 CFReleaseNull(error2
);
3639 stats
->averageSize
= totalSize
/ stats
->items
;
3648 CFReleaseNull(error
);
3650 query_destroy(q
, NULL
);
3654 CFArrayRef
_SecItemCopyParentCertificates(CFDataRef normalizedIssuer
, CFArrayRef accessGroups
, CFErrorRef
*error
) {
3655 const void *keys
[] = {
3662 kSecClassCertificate
,
3667 CFDictionaryRef query
= CFDictionaryCreate(NULL
, keys
, values
, 4,
3669 CFTypeRef results
= NULL
;
3670 SecurityClient client
= {
3672 .accessGroups
= accessGroups
,
3673 .allowSystemKeychain
= true,
3674 .allowSyncBubbleKeychain
= false,
3675 .isNetworkExtension
= false,
3678 (void)_SecItemCopyMatching(query
, &client
, &results
, error
);
3683 bool _SecItemCertificateExists(CFDataRef normalizedIssuer
, CFDataRef serialNumber
, CFArrayRef accessGroups
, CFErrorRef
*error
) {
3684 const void *keys
[] = {
3688 kSecAttrSerialNumber
3691 kSecClassCertificate
,
3696 SecurityClient client
= {
3698 .accessGroups
= accessGroups
,
3699 .allowSystemKeychain
= true,
3700 .allowSyncBubbleKeychain
= false,
3701 .isNetworkExtension
= false,
3703 CFDictionaryRef query
= CFDictionaryCreate(NULL
, keys
, values
, 4, NULL
, NULL
);
3704 CFTypeRef results
= NULL
;
3705 bool ok
= _SecItemCopyMatching(query
, &client
, &results
, error
);
3706 CFReleaseSafe(query
);
3707 CFReleaseSafe(results
);