2 * Copyright (c) 2006-2015 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/SOSViews.h>
44 // TODO: Make this include work on both platforms. rdar://problem/16526848
45 #if TARGET_OS_EMBEDDED
46 #include <Security/SecEntitlements.h>
48 /* defines from <Security/SecEntitlements.h> */
49 #define kSecEntitlementAssociatedDomains CFSTR("com.apple.developer.associated-domains")
50 #define kSecEntitlementPrivateAssociatedDomains CFSTR("com.apple.private.associated-domains")
53 #include <utilities/array_size.h>
54 #include <utilities/SecFileLocations.h>
55 #include <Security/SecuritydXPC.h>
56 #include "swcagent_client.h"
58 #if TARGET_OS_IPHONE && !TARGET_OS_NANO
60 #include <SharedWebCredentials/SharedWebCredentials.h>
62 typedef OSStatus (*SWCCheckService_f
)(CFStringRef service
, CFStringRef appID
, CFStringRef domain
, SWCCheckServiceCompletion_b completion
);
63 typedef OSStatus (*SWCSetServiceFlags_f
)(CFStringRef service
, CFStringRef appID
, CFStringRef domain
, SWCFlags mask
, SWCFlags flags
, SWCSetServiceFlagsCompletion_b completion
);
65 typedef uint32_t SWCFlags
;
66 #define kSWCFlags_None 0
67 #define kSWCFlag_Pending ( 1U << 0 )
68 #define kSWCFlag_SiteApproved ( 1U << 1 )
69 #define kSWCFlag_SiteDenied ( 1U << 2 )
70 #define kSWCFlag_UserApproved ( 1U << 3 )
71 #define kSWCFlag_UserDenied ( 1U << 4 )
72 #define kSWCFlag_ExternalMask ( kSWCFlag_UserApproved | kSWCFlag_UserDenied )
75 /* Changed the name of the keychain changed notification, for testing */
76 static const char *g_keychain_changed_notification
= kSecServerKeychainChangedNotification
;
78 void SecItemServerSetKeychainChangedNotification(const char *notification_name
)
80 g_keychain_changed_notification
= notification_name
;
83 void SecKeychainChanged(bool syncWithPeers
) {
84 uint32_t result
= notify_post(g_keychain_changed_notification
);
86 SOSCCSyncWithAllPeers();
87 if (result
== NOTIFY_STATUS_OK
)
88 secnotice("item", "Sent %s%s", syncWithPeers
? "SyncWithAllPeers and " : "", g_keychain_changed_notification
);
90 secerror("%snotify_post %s returned: %" PRIu32
, syncWithPeers
? "Sent SyncWithAllPeers, " : "", g_keychain_changed_notification
, result
);
93 /* Return the current database version in *version. */
94 static bool SecKeychainDbGetVersion(SecDbConnectionRef dbt
, int *version
, CFErrorRef
*error
)
96 __block
bool ok
= false;
97 SecDbQueryRef query
= NULL
;
98 __block CFNumberRef versionNumber
= NULL
;
99 __block CFErrorRef localError
= NULL
;
101 require_quiet(query
= query_create(&tversion_class
, NULL
, &localError
), out
);
102 require_quiet(SecDbItemSelect(query
, dbt
, &localError
, ^bool(const SecDbAttr
*attr
) {
103 // Bind all attributes.
105 }, ^bool(const SecDbAttr
*attr
) {
108 }, NULL
, NULL
, ^(SecDbItemRef item
, bool *stop
) {
109 versionNumber
= copyNumber(SecDbItemGetValue(item
, tversion_class
.attrs
[0], &localError
));
113 require_action_quiet(versionNumber
!= NULL
&& CFNumberGetValue(versionNumber
, kCFNumberIntType
, version
), out
,
114 // We have a tversion table but we didn't find a single version
115 // value, now what? I suppose we pretend the db is corrupted
116 // since this isn't supposed to ever happen.
117 SecDbError(SQLITE_CORRUPT
, error
, CFSTR("Failed to read version table"));
118 secwarning("tversion read error: %@", error
? *error
: NULL
));
122 if (!ok
&& CFErrorGetCode(localError
) == SQLITE_ERROR
) {
123 // Most probably means that the version table does not exist at all.
124 // TODO: Use "SELECT name FROM sqlite_master WHERE type='table' AND name='tversion'" to detect tversion presence.
125 CFReleaseSafe(localError
);
130 query_destroy(query
, NULL
);
131 CFReleaseSafe(versionNumber
);
132 return ok
|| CFErrorPropagate(localError
, error
);
135 // Goes through all tables represented by old_schema and tries to migrate all items from them into new (current version) tables.
136 static bool SecKeychainDbUpgradeFromSchema(SecDbConnectionRef dbt
, const SecDbSchema
*oldSchema
, bool *inProgress
, CFErrorRef
*error
) {
137 __block
bool ok
= true;
138 const SecDbSchema
*newSchema
= kc_schemas
[0];
139 SecDbClass
const *const *oldClass
;
140 SecDbClass
const *const *newClass
;
141 SecDbQueryRef query
= NULL
;
142 CFMutableStringRef sql
= NULL
;
144 // Rename existing tables to old names, as present in old schemas.
145 sql
= CFStringCreateMutable(NULL
, 0);
146 for (oldClass
= oldSchema
->classes
, newClass
= newSchema
->classes
;
147 *oldClass
!= NULL
&& *newClass
!= NULL
; oldClass
++, newClass
++) {
148 if (!CFEqual((*oldClass
)->name
, (*newClass
)->name
)) {
149 CFStringAppendFormat(sql
, NULL
, CFSTR("ALTER TABLE %@ RENAME TO %@;"),
150 (*newClass
)->name
, (*oldClass
)->name
);
152 CFStringAppendFormat(sql
, NULL
, CFSTR("DROP TABLE %@;"), (*oldClass
)->name
);
155 require_quiet(ok
&= SecDbExec(dbt
, sql
, error
), out
);
157 // Create tables for new schema.
158 require_quiet(ok
&= SecItemDbCreateSchema(dbt
, newSchema
, error
), out
);
159 // Go through all classes of current schema to transfer all items to new tables.
160 for (oldClass
= oldSchema
->classes
, newClass
= newSchema
->classes
;
161 *oldClass
!= NULL
&& *newClass
!= NULL
; oldClass
++, newClass
++) {
162 if (CFEqual((*oldClass
)->name
, (*newClass
)->name
))
165 // Prepare query to iterate through all items in cur_class.
167 query_destroy(query
, NULL
);
168 require_quiet(query
= query_create(*oldClass
, NULL
, error
), out
);
170 ok
&= SecDbItemSelect(query
, dbt
, error
, ^bool(const SecDbAttr
*attr
) {
171 // We are interested in all attributes which are physically present in the DB.
172 return (attr
->flags
& kSecDbInFlag
) != 0;
173 }, ^bool(const SecDbAttr
*attr
) {
174 // No filtering please.
176 }, NULL
, NULL
, ^(SecDbItemRef item
, bool *stop
) {
177 CFErrorRef localError
= NULL
;
179 // Switch item to the new class.
180 item
->class = *newClass
;
183 if (SecDbItemEnsureDecrypted(item
, &localError
)) {
184 // Delete SHA1 field from the item, so that it is newly recalculated before storing
185 // the item into the new table.
186 require_quiet(ok
&= SecDbItemSetValue(item
, SecDbClassAttrWithKind(item
->class, kSecDbSHA1Attr
, error
),
187 kCFNull
, error
), out
);
189 OSStatus status
= SecErrorGetOSStatus(localError
);
191 // Items producing errSecDecode are silently dropped - they are not decodable and lost forever.
192 require_quiet(status
!= errSecDecode
, out
);
194 // errSecAuthNeeded means that it is an ACL-based item which requires authentication (or at least
195 // ACM context, which we do not have). Other errors should abort the migration completely.
196 require_action_quiet(status
== errSecAuthNeeded
|| status
== errSecInteractionNotAllowed
, out
,
197 ok
&= CFErrorPropagate(localError
, error
); localError
= NULL
);
199 // If we've hit item which could not be decoded because of locked keybag, store it into
200 // new tables so that accessing it fails with errSecInteractionNotAllowed instead of errSecItemNotFound.
201 // Next connection to the DB with opened keybag will properly decode and replace the item.
202 if (status
== errSecInteractionNotAllowed
) {
206 // Leave item encrypted, do not ever try to decrypt it since it will fail.
207 item
->_edataState
= kSecDbItemAlwaysEncrypted
;
210 // Insert new item into the new table.
211 if (!SecDbItemInsert(item
, dbt
, &localError
)) {
212 secerror("item: %@ insert during upgrade: %@", item
, localError
);
213 ok
&= CFErrorPropagate(localError
, error
); localError
= NULL
;
217 CFReleaseSafe(localError
);
220 require_quiet(ok
, out
);
223 // Remove old tables from the DB.
224 CFAssignRetained(sql
, CFStringCreateMutable(NULL
, 0));
225 for (oldClass
= oldSchema
->classes
, newClass
= newSchema
->classes
;
226 *oldClass
!= NULL
&& *newClass
!= NULL
; oldClass
++, newClass
++) {
227 if (!CFEqual((*oldClass
)->name
, (*newClass
)->name
)) {
228 CFStringAppendFormat(sql
, NULL
, CFSTR("DROP TABLE %@;"), (*oldClass
)->name
);
231 require_quiet(ok
&= SecDbExec(dbt
, sql
, error
), out
);
235 query_destroy(query
, NULL
);
241 // Goes through all tables represented by old_schema and tries to migrate all items from them into new (current version) tables.
242 static bool SecKeychainDbUpgradeUnlockedItems(SecDbConnectionRef dbt
, bool *inProgress
, CFErrorRef
*error
) {
243 __block
bool ok
= true;
244 SecDbQueryRef query
= NULL
;
246 // Go through all classes in new schema
247 const SecDbSchema
*newSchema
= kc_schemas
[0];
248 for (const SecDbClass
*const *class = newSchema
->classes
; *class != NULL
&& !*inProgress
; class++) {
249 const SecDbAttr
*pdmn
= SecDbClassAttrWithKind(*class, kSecDbAccessAttr
, error
);
254 // Prepare query to go through all non-DK|DKU items
256 query_destroy(query
, NULL
);
258 require_action_quiet(query
= query_create(*class, NULL
, error
), out
, ok
= false);
259 ok
= SecDbItemSelect(query
, dbt
, error
, NULL
, ^bool(const SecDbAttr
*attr
) {
260 // No simple per-attribute filtering.
262 }, ^bool(CFMutableStringRef sql
, bool *needWhere
) {
263 // Select only non-D-class items
264 SecDbAppendWhereOrAnd(sql
, needWhere
);
265 CFStringAppendFormat(sql
, NULL
, CFSTR("NOT %@ IN (?,?)"), pdmn
->name
);
267 }, ^bool(sqlite3_stmt
*stmt
, int col
) {
268 return SecDbBindObject(stmt
, col
++, kSecAttrAccessibleAlways
, error
) &&
269 SecDbBindObject(stmt
, col
++, kSecAttrAccessibleAlwaysThisDeviceOnly
, error
);
270 }, ^(SecDbItemRef item
, bool *stop
) {
271 CFErrorRef localError
= NULL
;
274 if (SecDbItemEnsureDecrypted(item
, &localError
)) {
275 // Delete SHA1 field from the item, so that it is newly recalculated before storing
276 // the item into the new table.
277 require_quiet(ok
= SecDbItemSetValue(item
, SecDbClassAttrWithKind(item
->class, kSecDbSHA1Attr
, error
),
278 kCFNull
, error
), out
);
280 // Replace item with the new value in the table; this will cause the item to be decoded and recoded back,
281 // incl. recalculation of item's hash.
282 ok
= SecDbItemUpdate(item
, item
, dbt
, false, error
);
284 CFIndex status
= CFErrorGetCode(localError
);
286 // Items producing errSecDecode are silently dropped - they are not decodable and lost forever.
287 require_action_quiet(status
!= errSecDecode
, out
, ok
= SecDbItemDelete(item
, dbt
, false, error
));
289 // If we are still not able to decrypt the item because the class key is not released yet,
290 // remember that DB still needs phase2 migration to be run next time a connection is made. Also
291 // stop iterating next items, it would be just waste of time because the whole iteration will be run
292 // next time when this phase2 will be rerun.
293 if (status
== errSecInteractionNotAllowed
) {
297 // errSecAuthNeeded means that it is an ACL-based item which requires authentication (or at least
298 // ACM context, which we do not have). Other errors should abort the migration completely.
299 require_action_quiet(status
== errSecAuthNeeded
, out
,
300 ok
= CFErrorPropagate(CFRetainSafe(localError
), error
));
305 CFReleaseSafe(localError
);
306 *stop
= *stop
|| !ok
;
314 query_destroy(query
, NULL
);
318 static bool SecKeychainDbUpgradeFromVersion(SecDbConnectionRef dbt
, int version
, bool *inProgress
, CFErrorRef
*error
) {
319 __block
bool ok
= true;
321 // The schema we want to have is the first in the list of schemas.
322 const SecDbSchema
*newSchema
= kc_schemas
[0];
324 // If DB schema is the one we want, we are done.
325 require_quiet(newSchema
->version
!= version
, out
);
328 // Pre v6 keychains need to have WAL enabled, since SecDb only does this at db creation time.
329 // NOTE: This has to be run outside of a transaction.
330 require_action_quiet(ok
= (SecDbExec(dbt
, CFSTR("PRAGMA auto_vacuum = FULL"), error
) &&
331 SecDbExec(dbt
, CFSTR("PRAGMA journal_mode = WAL"), error
)),
332 out
, secerror("unable to enable WAL or auto vacuum, marking DB as corrupt: %@",
333 error
? *error
: NULL
));
336 ok
&= SecDbTransaction(dbt
, kSecDbExclusiveTransactionType
, error
, ^(bool *commit
) {
337 CFStringRef sql
= NULL
;
339 // Get version again once we start a transaction, someone else might change the migration state.
341 require_quiet(ok
= SecKeychainDbGetVersion(dbt
, &version
, error
), out
);
342 require_quiet(newSchema
->version
!= version
, out
);
344 // If this is empty database, just create table according to schema and be done with it.
345 require_action_quiet(version
!= 0, out
, ok
= SecItemDbCreateSchema(dbt
, newSchema
, error
));
347 int oldVersion
= (version
>> 16) & 0xffff;
349 require_action_quiet(version
== newSchema
->version
|| oldVersion
== 0, out
,
350 ok
= SecDbError(SQLITE_CORRUPT
, error
,
351 CFSTR("Half migrated but obsolete DB found: found %d(%d) but %d is needed"),
352 version
, oldVersion
, newSchema
->version
));
354 // Check whether we have both old and new tables in the DB.
355 if (oldVersion
== 0) {
356 // Pure old-schema migration attempt, with full blown table renames etc (a.k.a. phase1)
357 oldVersion
= version
;
358 version
= newSchema
->version
;
360 // Find schema for old database.
361 const SecDbSchema
*oldSchema
= NULL
;
362 for (const SecDbSchema
* const *pschema
= kc_schemas
; *pschema
; ++pschema
) {
363 if ((*pschema
)->version
== oldVersion
) {
364 oldSchema
= *pschema
;
369 // If we are attempting to upgrade from a version for which we have no schema, fail.
370 require_action_quiet(oldSchema
!= NULL
, out
,
371 ok
= SecDbError(SQLITE_CORRUPT
, error
, CFSTR("no schema for version: %d"), oldVersion
);
372 secerror("no schema for version %d", oldVersion
));
374 require(ok
= SecKeychainDbUpgradeFromSchema(dbt
, oldSchema
, inProgress
, error
), out
);
376 // Just go through non-D-class items in new tables and apply decode/encode on them, because
377 // they were not recoded completely during some previous old-schema migration attempt (a.k.a. phase2)
378 require(ok
= SecKeychainDbUpgradeUnlockedItems(dbt
, inProgress
, error
), out
);
382 // If either migration path we did reported that the migration was complete, signalize that
383 // in the version database by cleaning oldVersion (which is stored in upper halfword of the version)
387 // Update database version table.
388 version
|= oldVersion
<< 16;
389 sql
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("UPDATE %@ SET %@ = %d"),
390 tversion_class
.name
, tversion_class
.attrs
[0]->name
, version
);
391 require_quiet(ok
= SecDbExec(dbt
, sql
, error
), out
);
400 secerror("unable to complete upgrade, marking DB as corrupt: %@", error
? *error
: NULL
);
407 /* AUDIT[securityd](done):
408 accessGroup (ok) is a caller provided, non NULL CFTypeRef.
410 Return true iff accessGroup is allowable according to accessGroups.
412 static bool accessGroupsAllows(CFArrayRef accessGroups
,
413 CFStringRef accessGroup
) {
414 /* NULL accessGroups is wildcard. */
417 /* Make sure we have a string. */
418 if (!isString(accessGroup
))
421 /* Having the special accessGroup "*" allows access to all accessGroups. */
422 CFRange range
= { 0, CFArrayGetCount(accessGroups
) };
424 (CFArrayContainsValue(accessGroups
, range
, accessGroup
) ||
425 CFArrayContainsValue(accessGroups
, range
, CFSTR("*"))))
431 bool itemInAccessGroup(CFDictionaryRef item
, CFArrayRef accessGroups
) {
432 return accessGroupsAllows(accessGroups
,
433 CFDictionaryGetValue(item
, kSecAttrAccessGroup
));
437 static CF_RETURNS_RETAINED CFDataRef
SecServerExportBackupableKeychain(SecDbConnectionRef dbt
,
438 keybag_handle_t src_keybag
, keybag_handle_t dest_keybag
, CFErrorRef
*error
) {
439 CFDataRef data_out
= NULL
;
440 /* Export everything except the items for which SecItemIsSystemBound()
442 CFDictionaryRef keychain
= SecServerExportKeychainPlist(dbt
,
443 src_keybag
, dest_keybag
, kSecBackupableItemFilter
,
446 data_out
= CFPropertyListCreateData(kCFAllocatorDefault
, keychain
,
447 kCFPropertyListBinaryFormat_v1_0
,
455 static bool SecServerImportBackupableKeychain(SecDbConnectionRef dbt
,
456 keybag_handle_t src_keybag
,
457 keybag_handle_t dest_keybag
, CFDataRef data
, CFErrorRef
*error
) {
458 return kc_transaction(dbt
, error
, ^{
460 CFDictionaryRef keychain
;
461 keychain
= CFPropertyListCreateWithData(kCFAllocatorDefault
, data
,
462 kCFPropertyListImmutable
, NULL
,
465 if (isDictionary(keychain
)) {
466 ok
= SecServerImportKeychainInPlist(dbt
, src_keybag
,
467 dest_keybag
, keychain
,
468 kSecBackupableItemFilter
,
471 ok
= SecError(errSecParam
, error
, CFSTR("import: keychain is not a dictionary"));
479 static CF_RETURNS_RETAINED CFDataRef
SecServerKeychainBackup(SecDbConnectionRef dbt
, CFDataRef keybag
,
480 CFDataRef password
, CFErrorRef
*error
) {
481 CFDataRef backup
= NULL
;
482 keybag_handle_t backup_keybag
;
483 if (ks_open_keybag(keybag
, password
, &backup_keybag
, error
)) {
484 /* Export from system keybag to backup keybag. */
485 backup
= SecServerExportBackupableKeychain(dbt
, KEYBAG_DEVICE
, backup_keybag
, error
);
486 if (!ks_close_keybag(backup_keybag
, error
)) {
487 CFReleaseNull(backup
);
493 static bool SecServerKeychainRestore(SecDbConnectionRef dbt
, CFDataRef backup
,
494 CFDataRef keybag
, CFDataRef password
, CFErrorRef
*error
) {
495 keybag_handle_t backup_keybag
;
496 if (!ks_open_keybag(keybag
, password
, &backup_keybag
, error
))
499 /* Import from backup keybag to system keybag. */
500 bool ok
= SecServerImportBackupableKeychain(dbt
, backup_keybag
, KEYBAG_DEVICE
,
502 ok
&= ks_close_keybag(backup_keybag
, error
);
508 // MARK - External SPI support code.
510 CFStringRef
__SecKeychainCopyPath(void) {
511 CFStringRef kcRelPath
= NULL
;
513 kcRelPath
= CFSTR("keychain-2.db");
515 kcRelPath
= CFSTR("keychain-2-debug.db");
518 CFStringRef kcPath
= NULL
;
519 CFURLRef kcURL
= SecCopyURLForFileInKeychainDirectory(kcRelPath
);
521 kcPath
= CFURLCopyFileSystemPath(kcURL
, kCFURLPOSIXPathStyle
);
528 // MARK: kc_dbhandle init and reset
530 SecDbRef
SecKeychainDbCreate(CFStringRef path
) {
531 return SecDbCreate(path
, ^bool (SecDbConnectionRef dbconn
, bool didCreate
, bool *callMeAgainForNextConnection
, CFErrorRef
*error
) {
532 // Upgrade from version 0 means create the schema in empty db.
536 ok
= SecKeychainDbGetVersion(dbconn
, &version
, error
);
538 ok
= ok
&& SecKeychainDbUpgradeFromVersion(dbconn
, version
, callMeAgainForNextConnection
, error
);
540 secerror("Upgrade %sfailed: %@", didCreate
? "from v0 " : "", error
? *error
: NULL
);
546 static SecDbRef _kc_dbhandle
= NULL
;
548 static void kc_dbhandle_init(void) {
549 SecDbRef oldHandle
= _kc_dbhandle
;
551 CFStringRef dbPath
= __SecKeychainCopyPath();
553 _kc_dbhandle
= SecKeychainDbCreate(dbPath
);
556 secerror("no keychain path available");
559 secerror("replaced %@ with %@", oldHandle
, _kc_dbhandle
);
560 CFRelease(oldHandle
);
564 // A callback for the sqlite3_log() interface.
565 static void sqlite3Log(void *pArg
, int iErrCode
, const char *zMsg
){
566 secinfo("sqlite3", "(%d) %s", iErrCode
, zMsg
);
569 static void setup_sqlite3_defaults_settings() {
570 int rx
= sqlite3_config(SQLITE_CONFIG_LOG
, sqlite3Log
, NULL
);
571 if (SQLITE_OK
!= rx
) {
572 secwarning("Could not set up sqlite global error logging to syslog: %d", rx
);
576 static dispatch_once_t _kc_dbhandle_once
;
578 static SecDbRef
kc_dbhandle(void) {
579 dispatch_once(&_kc_dbhandle_once
, ^{
580 setup_sqlite3_defaults_settings();
586 /* For whitebox testing only */
587 void kc_dbhandle_reset(void);
588 void kc_dbhandle_reset(void)
590 __block
bool done
= false;
591 dispatch_once(&_kc_dbhandle_once
, ^{
595 // TODO: Not thread safe at all! - FOR DEBUGGING ONLY
600 static SecDbConnectionRef
kc_aquire_dbt(bool writeAndRead
, CFErrorRef
*error
) {
601 SecDbRef db
= kc_dbhandle();
603 SecError(errSecDataNotAvailable
, error
, CFSTR("failed to get a db handle"));
606 return SecDbConnectionAquire(db
, !writeAndRead
, error
);
609 /* Return a per thread dbt handle for the keychain. If create is true create
610 the database if it does not yet exist. If it is false, just return an
611 error if it fails to auto-create. */
612 static bool kc_with_dbt(bool writeAndRead
, CFErrorRef
*error
, bool (^perform
)(SecDbConnectionRef dbt
))
614 // Make sure we initialize our engines before writing to the keychain
616 SecItemDataSourceFactoryGetDefault();
619 SecDbConnectionRef dbt
= kc_aquire_dbt(writeAndRead
, error
);
622 SecDbConnectionRelease(dbt
);
628 items_matching_issuer_parent(SecDbConnectionRef dbt
, CFArrayRef accessGroups
,
629 CFDataRef issuer
, CFArrayRef issuers
, int recurse
)
632 CFArrayRef results
= NULL
;
636 if (CFArrayContainsValue(issuers
, CFRangeMake(0, CFArrayGetCount(issuers
)), issuer
))
639 const void *keys
[] = { kSecClass
, kSecReturnRef
, kSecAttrSubject
};
640 const void *vals
[] = { kSecClassCertificate
, kCFBooleanTrue
, issuer
};
641 CFDictionaryRef query
= CFDictionaryCreate(kCFAllocatorDefault
, keys
, vals
, array_size(keys
), NULL
, NULL
);
646 CFErrorRef localError
= NULL
;
647 q
= query_create_with_limit(query
, kSecMatchUnlimited
, &localError
);
650 s3dl_copy_matching(dbt
, q
, (CFTypeRef
*)&results
, accessGroups
, &localError
);
651 query_destroy(q
, &localError
);
654 secerror("items matching issuer parent: %@", localError
);
655 CFReleaseNull(localError
);
659 count
= CFArrayGetCount(results
);
660 for (i
= 0; (i
< count
) && !found
; i
++) {
661 CFDictionaryRef cert_dict
= (CFDictionaryRef
)CFArrayGetValueAtIndex(results
, i
);
662 CFDataRef cert_issuer
= CFDictionaryGetValue(cert_dict
, kSecAttrIssuer
);
663 if (CFEqual(cert_issuer
, issuer
))
666 found
= items_matching_issuer_parent(dbt
, accessGroups
, cert_issuer
, issuers
, recurse
);
668 CFReleaseSafe(results
);
673 bool match_item(SecDbConnectionRef dbt
, Query
*q
, CFArrayRef accessGroups
, CFDictionaryRef item
)
675 if (q
->q_match_issuer
) {
676 CFDataRef issuer
= CFDictionaryGetValue(item
, kSecAttrIssuer
);
677 if (!items_matching_issuer_parent(dbt
, accessGroups
, issuer
, q
->q_match_issuer
, 10 /*max depth*/))
681 /* Add future match checks here. */
686 /****************************************************************************
687 **************** Beginning of Externally Callable Interface ****************
688 ****************************************************************************/
691 // TODO Use as a safety wrapper
692 static bool SecErrorWith(CFErrorRef
*in_error
, bool (^perform
)(CFErrorRef
*error
)) {
693 CFErrorRef error
= in_error
? *in_error
: NULL
;
695 if ((ok
= perform(&error
))) {
696 assert(error
== NULL
);
698 secerror("error + success: %@", error
);
701 OSStatus status
= SecErrorGetOSStatus(error
);
702 if (status
!= errSecItemNotFound
) // Occurs in normal operation, so exclude
703 secerror("error:[%" PRIdOSStatus
"] %@", status
, error
);
707 CFReleaseNull(error
);
714 /* AUDIT[securityd](done):
715 query (ok) is a caller provided dictionary, only its cf type has been checked.
718 SecItemServerCopyMatching(CFDictionaryRef query
, CFTypeRef
*result
,
719 CFArrayRef accessGroups
, CFErrorRef
*error
)
722 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
))) {
723 return SecError(errSecMissingEntitlement
, error
,
724 CFSTR("client has neither application-identifier nor keychain-access-groups entitlements"));
727 if (CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), CFSTR("*"))) {
728 /* Having the special accessGroup "*" allows access to all accessGroups. */
733 Query
*q
= query_create_with_limit(query
, 1, error
);
735 CFStringRef agrp
= CFDictionaryGetValue(q
->q_item
, kSecAttrAccessGroup
);
736 if (agrp
&& accessGroupsAllows(accessGroups
, agrp
)) {
737 // TODO: Return an error if agrp is not NULL and accessGroupsAllows() fails above.
738 const void *val
= agrp
;
739 accessGroups
= CFArrayCreate(0, &val
, 1, &kCFTypeArrayCallBacks
);
741 CFRetainSafe(accessGroups
);
744 query_set_caller_access_groups(q
, accessGroups
);
746 /* Sanity check the query. */
747 if (q
->q_use_item_list
) {
748 ok
= SecError(errSecUseItemListUnsupported
, error
, CFSTR("use item list unsupported"));
749 #if defined(MULTIPLE_KEYCHAINS)
750 } else if (q
->q_use_keychain
) {
751 ok
= SecError(errSecUseKeychainUnsupported
, error
, CFSTR("use keychain list unsupported"));
753 } else if (q
->q_match_issuer
&& ((q
->q_class
!= &cert_class
) &&
754 (q
->q_class
!= &identity_class
))) {
755 ok
= SecError(errSecUnsupportedOperation
, error
, CFSTR("unsupported match attribute"));
756 } else if (q
->q_return_type
!= 0 && result
== NULL
) {
757 ok
= SecError(errSecReturnMissingPointer
, error
, CFSTR("missing pointer"));
758 } else if (!q
->q_error
) {
759 ok
= kc_with_dbt(false, error
, ^(SecDbConnectionRef dbt
) {
760 return s3dl_copy_matching(dbt
, q
, result
, accessGroups
, error
);
764 CFReleaseSafe(accessGroups
);
765 if (!query_destroy(q
, error
))
773 _SecItemCopyMatching(CFDictionaryRef query
, CFArrayRef accessGroups
, CFTypeRef
*result
, CFErrorRef
*error
) {
774 return SecItemServerCopyMatching(query
, result
, accessGroups
, error
);
777 /* AUDIT[securityd](done):
778 attributes (ok) is a caller provided dictionary, only its cf type has
782 _SecItemAdd(CFDictionaryRef attributes
, CFArrayRef accessGroups
,
783 CFTypeRef
*result
, CFErrorRef
*error
)
787 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
))) {
788 return SecError(errSecMissingEntitlement
, error
,
789 CFSTR("client has neither application-identifier nor keychain-access-groups entitlements"));
792 Query
*q
= query_create_with_limit(attributes
, 0, error
);
794 /* Access group sanity checking. */
795 CFStringRef agrp
= (CFStringRef
)CFDictionaryGetValue(attributes
,
796 kSecAttrAccessGroup
);
798 CFArrayRef ag
= accessGroups
;
799 /* Having the special accessGroup "*" allows access to all accessGroups. */
800 if (CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), CFSTR("*")))
804 /* The user specified an explicit access group, validate it. */
805 if (!accessGroupsAllows(accessGroups
, agrp
))
806 ok
= SecError(errSecNoAccessForItem
, error
, CFSTR("NoAccessForItem"));
808 agrp
= (CFStringRef
)CFArrayGetValueAtIndex(ag
, 0);
810 /* We are using an implicit access group, add it as if the user
811 specified it as an attribute. */
812 query_add_attribute(kSecAttrAccessGroup
, agrp
, q
);
816 query_ensure_access_control(q
, agrp
);
819 ok
= SecError(errSecValuePersistentRefUnsupported
, error
, CFSTR("q_row_id")); // TODO: better error string
820 #if defined(MULTIPLE_KEYCHAINS)
821 else if (q
->q_use_keychain_list
)
822 ok
= SecError(errSecUseKeychainListUnsupported
, error
, CFSTR("q_use_keychain_list")); // TODO: better error string;
824 else if (!q
->q_error
) {
825 ok
= kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
){
826 return kc_transaction(dbt
, error
, ^{
827 query_pre_add(q
, true);
828 return s3dl_query_add(dbt
, q
, result
, error
);
833 ok
= query_notify_and_destroy(q
, ok
, error
);
840 /* AUDIT[securityd](done):
841 query (ok) and attributesToUpdate (ok) are a caller provided dictionaries,
842 only their cf types have been checked.
845 _SecItemUpdate(CFDictionaryRef query
, CFDictionaryRef attributesToUpdate
,
846 CFArrayRef accessGroups
, CFErrorRef
*error
)
849 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
))) {
850 return SecError(errSecMissingEntitlement
, error
,
851 CFSTR("client has neither application-identifier nor keychain-access-groups entitlements"));
854 if (CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), CFSTR("*"))) {
855 /* Having the special accessGroup "*" allows access to all accessGroups. */
860 Query
*q
= query_create_with_limit(query
, kSecMatchUnlimited
, error
);
865 /* Sanity check the query. */
866 query_set_caller_access_groups(q
, accessGroups
);
867 if (q
->q_use_item_list
) {
868 ok
= SecError(errSecUseItemListUnsupported
, error
, CFSTR("use item list not supported"));
869 } else if (q
->q_return_type
& kSecReturnDataMask
) {
870 /* Update doesn't return anything so don't ask for it. */
871 ok
= SecError(errSecReturnDataUnsupported
, error
, CFSTR("return data not supported by update"));
872 } else if (q
->q_return_type
& kSecReturnAttributesMask
) {
873 ok
= SecError(errSecReturnAttributesUnsupported
, error
, CFSTR("return attributes not supported by update"));
874 } else if (q
->q_return_type
& kSecReturnRefMask
) {
875 ok
= SecError(errSecReturnRefUnsupported
, error
, CFSTR("return ref not supported by update"));
876 } else if (q
->q_return_type
& kSecReturnPersistentRefMask
) {
877 ok
= SecError(errSecReturnPersistentRefUnsupported
, error
, CFSTR("return persistent ref not supported by update"));
879 /* Access group sanity checking. */
880 CFStringRef agrp
= (CFStringRef
)CFDictionaryGetValue(attributesToUpdate
,
881 kSecAttrAccessGroup
);
883 /* The user is attempting to modify the access group column,
884 validate it to make sure the new value is allowable. */
885 if (!accessGroupsAllows(accessGroups
, agrp
)) {
886 ok
= SecError(errSecNoAccessForItem
, error
, CFSTR("accessGroup %@ not in %@"), agrp
, accessGroups
);
892 ok
= kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
893 return s3dl_query_update(dbt
, q
, attributesToUpdate
, accessGroups
, error
);
897 ok
= query_notify_and_destroy(q
, ok
, error
);
903 /* AUDIT[securityd](done):
904 query (ok) is a caller provided dictionary, only its cf type has been checked.
907 _SecItemDelete(CFDictionaryRef query
, CFArrayRef accessGroups
, CFErrorRef
*error
)
910 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
))) {
911 return SecError(errSecMissingEntitlement
, error
,
912 CFSTR("client has neither application-identifier nor keychain-access-groups entitlements"));
915 if (CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), CFSTR("*"))) {
916 /* Having the special accessGroup "*" allows access to all accessGroups. */
920 Query
*q
= query_create_with_limit(query
, kSecMatchUnlimited
, error
);
923 query_set_caller_access_groups(q
, accessGroups
);
924 /* Sanity check the query. */
925 if (q
->q_limit
!= kSecMatchUnlimited
)
926 ok
= SecError(errSecMatchLimitUnsupported
, error
, CFSTR("match limit not supported by delete"));
927 else if (query_match_count(q
) != 0)
928 ok
= SecError(errSecItemMatchUnsupported
, error
, CFSTR("match not supported by delete"));
930 ok
= SecError(errSecValueRefUnsupported
, error
, CFSTR("value ref not supported by delete"));
931 else if (q
->q_row_id
&& query_attr_count(q
))
932 ok
= SecError(errSecItemIllegalQuery
, error
, CFSTR("rowid and other attributes are mutually exclusive"));
934 ok
= kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
935 return s3dl_query_delete(dbt
, q
, accessGroups
, error
);
938 ok
= query_notify_and_destroy(q
, ok
, error
);
946 /* AUDIT[securityd](done):
947 No caller provided inputs.
950 SecItemServerDeleteAll(CFErrorRef
*error
) {
951 return kc_with_dbt(true, error
, ^bool (SecDbConnectionRef dbt
) {
952 return (kc_transaction(dbt
, error
, ^bool {
953 return (SecDbExec(dbt
, CFSTR("DELETE from genp;"), error
) &&
954 SecDbExec(dbt
, CFSTR("DELETE from inet;"), error
) &&
955 SecDbExec(dbt
, CFSTR("DELETE from cert;"), error
) &&
956 SecDbExec(dbt
, CFSTR("DELETE from keys;"), error
));
957 }) && SecDbExec(dbt
, CFSTR("VACUUM;"), error
));
962 _SecItemDeleteAll(CFErrorRef
*error
) {
963 return SecItemServerDeleteAll(error
);
968 // MARK: Shared web credentials
971 #define SEC_CONST_DECL(k,v) const CFStringRef k = CFSTR(v);
973 SEC_CONST_DECL (kSecSafariAccessGroup
, "com.apple.cfnetwork");
974 SEC_CONST_DECL (kSecSafariDefaultComment
, "default");
975 SEC_CONST_DECL (kSecSafariPasswordsNotSaved
, "Passwords not saved");
976 SEC_CONST_DECL (kSecSharedCredentialUrlScheme
, "https://");
977 SEC_CONST_DECL (kSecSharedWebCredentialsService
, "webcredentials");
979 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
980 static dispatch_once_t sSecSWCInitializeOnce
= 0;
981 static void * sSecSWCLibrary
= NULL
;
982 static SWCCheckService_f sSWCCheckService_f
= NULL
;
983 static SWCSetServiceFlags_f sSWCSetServiceFlags_f
= NULL
;
985 static OSStatus
_SecSWCEnsuredInitialized(void);
987 static OSStatus
_SecSWCEnsuredInitialized(void)
989 __block OSStatus status
= errSecNotAvailable
;
991 dispatch_once(&sSecSWCInitializeOnce
, ^{
992 sSecSWCLibrary
= dlopen("/System/Library/PrivateFrameworks/SharedWebCredentials.framework/SharedWebCredentials", RTLD_LAZY
| RTLD_LOCAL
);
993 assert(sSecSWCLibrary
);
994 if (sSecSWCLibrary
) {
995 sSWCCheckService_f
= (SWCCheckService_f
)(uintptr_t) dlsym(sSecSWCLibrary
, "SWCCheckService");
996 sSWCSetServiceFlags_f
= (SWCSetServiceFlags_f
)(uintptr_t) dlsym(sSecSWCLibrary
, "SWCSetServiceFlags");
1000 if (sSWCCheckService_f
&& sSWCSetServiceFlags_f
) {
1007 #if !TARGET_IPHONE_SIMULATOR
1009 _SecAppDomainApprovalStatus(CFStringRef appID
, CFStringRef fqdn
, CFErrorRef
*error
)
1011 __block SWCFlags flags
= kSWCFlags_None
;
1013 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1014 OSStatus status
= _SecSWCEnsuredInitialized();
1016 SecError(status
, error
, CFSTR("SWC initialize failed"));
1019 CFRetainSafe(appID
);
1021 dispatch_semaphore_t semaphore
= dispatch_semaphore_create(0);
1022 dispatch_retain(semaphore
);
1023 if (0 == sSWCCheckService_f(kSecSharedWebCredentialsService
, appID
, fqdn
,
1024 ^void (OSStatus inStatus
, SWCFlags inFlags
, CFDictionaryRef inDetails
) {
1025 if (!inStatus
) { flags
= inFlags
; }
1026 CFReleaseSafe(appID
);
1027 CFReleaseSafe(fqdn
);
1028 dispatch_semaphore_signal(semaphore
);
1029 dispatch_release(semaphore
);
1030 //secerror("SWCCheckService: inStatus=%d, flags=%0X", inStatus, flags);
1033 // wait for the block to complete, as we need its answer
1034 dispatch_semaphore_wait(semaphore
, DISPATCH_TIME_FOREVER
);
1036 else // didn't queue the block
1038 CFReleaseSafe(appID
);
1039 CFReleaseSafe(fqdn
);
1040 dispatch_release(semaphore
);
1042 dispatch_release(semaphore
);
1044 flags
|= (kSWCFlag_SiteApproved
);
1047 if (!error
) { return flags
; }
1050 // check website approval status
1051 if (!(flags
& kSWCFlag_SiteApproved
)) {
1052 if (flags
& kSWCFlag_Pending
) {
1053 SecError(errSecAuthFailed
, error
, CFSTR("Approval is pending for \"%@\", try later"), fqdn
);
1055 SecError(errSecAuthFailed
, error
, CFSTR("\"%@\" failed to approve \"%@\""), fqdn
, appID
);
1060 // check user approval status
1061 if (flags
& kSWCFlag_UserDenied
) {
1062 SecError(errSecAuthFailed
, error
, CFSTR("User denied access to \"%@\" by \"%@\""), fqdn
, appID
);
1068 #if !TARGET_IPHONE_SIMULATOR
1070 _SecEntitlementContainsDomainForService(CFArrayRef domains
, CFStringRef domain
, CFStringRef service
)
1072 bool result
= false;
1073 CFIndex idx
, count
= (domains
) ? CFArrayGetCount(domains
) : (CFIndex
) 0;
1074 if (!count
|| !domain
|| !service
) {
1077 for (idx
=0; idx
< count
; idx
++) {
1078 CFStringRef str
= (CFStringRef
) CFArrayGetValueAtIndex(domains
, idx
);
1079 if (str
&& CFStringHasPrefix(str
, kSecSharedWebCredentialsService
)) {
1080 CFIndex prefix_len
= CFStringGetLength(kSecSharedWebCredentialsService
)+1;
1081 CFIndex substr_len
= CFStringGetLength(str
) - prefix_len
;
1082 CFRange range
= { prefix_len
, substr_len
};
1083 CFStringRef substr
= CFStringCreateWithSubstring(kCFAllocatorDefault
, str
, range
);
1084 if (substr
&& CFEqual(substr
, domain
)) {
1087 CFReleaseSafe(substr
);
1098 _SecAddNegativeWebCredential(CFStringRef fqdn
, CFStringRef appID
, bool forSafari
)
1100 bool result
= false;
1101 if (!fqdn
) { return result
; }
1103 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1104 OSStatus status
= _SecSWCEnsuredInitialized();
1105 if (status
) { return false; }
1107 // update our database
1108 CFRetainSafe(appID
);
1110 if (0 == sSWCSetServiceFlags_f(kSecSharedWebCredentialsService
,
1111 appID
, fqdn
, kSWCFlag_ExternalMask
, kSWCFlag_UserDenied
,
1112 ^void(OSStatus inStatus
, SWCFlags inNewFlags
){
1113 CFReleaseSafe(appID
);
1114 CFReleaseSafe(fqdn
);
1119 else // didn't queue the block
1121 CFReleaseSafe(appID
);
1122 CFReleaseSafe(fqdn
);
1125 if (!forSafari
) { return result
; }
1127 // below this point: create a negative Safari web credential item
1129 CFMutableDictionaryRef attrs
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1130 if (!attrs
) { return result
; }
1132 CFErrorRef error
= NULL
;
1133 CFStringRef accessGroup
= CFSTR("*");
1134 CFArrayRef accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&accessGroup
, 1, &kCFTypeArrayCallBacks
);
1136 CFDictionaryAddValue(attrs
, kSecClass
, kSecClassInternetPassword
);
1137 CFDictionaryAddValue(attrs
, kSecAttrAccessGroup
, kSecSafariAccessGroup
);
1138 CFDictionaryAddValue(attrs
, kSecAttrAuthenticationType
, kSecAttrAuthenticationTypeHTMLForm
);
1139 CFDictionaryAddValue(attrs
, kSecAttrProtocol
, kSecAttrProtocolHTTPS
);
1140 CFDictionaryAddValue(attrs
, kSecAttrServer
, fqdn
);
1141 CFDictionaryAddValue(attrs
, kSecAttrSynchronizable
, kCFBooleanTrue
);
1143 (void)_SecItemDelete(attrs
, accessGroups
, &error
);
1144 CFReleaseNull(error
);
1146 CFDictionaryAddValue(attrs
, kSecAttrAccount
, kSecSafariPasswordsNotSaved
);
1147 CFDictionaryAddValue(attrs
, kSecAttrComment
, kSecSafariDefaultComment
);
1149 CFStringRef label
= CFStringCreateWithFormat(kCFAllocatorDefault
,
1150 NULL
, CFSTR("%@ (%@)"), fqdn
, kSecSafariPasswordsNotSaved
);
1152 CFDictionaryAddValue(attrs
, kSecAttrLabel
, label
);
1153 CFReleaseSafe(label
);
1157 CFDataRef data
= CFDataCreate(kCFAllocatorDefault
, &space
, 1);
1159 CFDictionarySetValue(attrs
, kSecValueData
, data
);
1160 CFReleaseSafe(data
);
1163 CFTypeRef addResult
= NULL
;
1164 result
= _SecItemAdd(attrs
, accessGroups
, &addResult
, &error
);
1166 CFReleaseSafe(addResult
);
1167 CFReleaseSafe(error
);
1168 CFReleaseSafe(attrs
);
1169 CFReleaseSafe(accessGroups
);
1174 /* Specialized version of SecItemAdd for shared web credentials */
1176 _SecAddSharedWebCredential(CFDictionaryRef attributes
,
1177 const audit_token_t
*clientAuditToken
,
1181 CFErrorRef
*error
) {
1183 CFStringRef fqdn
= CFDictionaryGetValue(attributes
, kSecAttrServer
);
1184 CFStringRef account
= CFDictionaryGetValue(attributes
, kSecAttrAccount
);
1185 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1186 CFStringRef password
= CFDictionaryGetValue(attributes
, kSecSharedPassword
);
1188 CFStringRef password
= CFDictionaryGetValue(attributes
, CFSTR("spwd"));
1190 CFStringRef accessGroup
= CFSTR("*");
1191 CFArrayRef accessGroups
= NULL
;
1192 CFMutableDictionaryRef query
= NULL
, attrs
= NULL
;
1194 bool ok
= false, update
= false;
1195 //bool approved = false;
1197 // check autofill enabled status
1198 if (!swca_autofill_enabled(clientAuditToken
)) {
1199 SecError(errSecBadReq
, error
, CFSTR("Autofill is not enabled in Safari settings"));
1203 // parse fqdn with CFURL here, since it could be specified as domain:port
1206 CFStringRef urlStr
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%@%@"), kSecSharedCredentialUrlScheme
, fqdn
);
1208 CFURLRef url
= CFURLCreateWithString(kCFAllocatorDefault
, urlStr
, nil
);
1210 CFStringRef hostname
= CFURLCopyHostName(url
);
1212 CFReleaseSafe(fqdn
);
1214 port
= CFURLGetPortNumber(url
);
1218 CFReleaseSafe(urlStr
);
1223 SecError(errSecParam
, error
, CFSTR("No account provided"));
1227 SecError(errSecParam
, error
, CFSTR("No domain provided"));
1231 #if TARGET_IPHONE_SIMULATOR
1232 secerror("app/site association entitlements not checked in Simulator");
1234 OSStatus status
= errSecMissingEntitlement
;
1235 // validate that fqdn is part of caller's shared credential domains entitlement
1237 SecError(status
, error
, CFSTR("Missing application-identifier entitlement"));
1240 if (_SecEntitlementContainsDomainForService(domains
, fqdn
, kSecSharedWebCredentialsService
)) {
1241 status
= errSecSuccess
;
1243 if (errSecSuccess
!= status
) {
1244 CFStringRef msg
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
,
1245 CFSTR("%@ not found in %@ entitlement"), fqdn
, kSecEntitlementAssociatedDomains
);
1247 msg
= CFRetain(CFSTR("Requested domain not found in entitlement"));
1249 SecError(status
, error
, CFSTR("%@"), msg
);
1255 #if TARGET_IPHONE_SIMULATOR
1256 secerror("Ignoring app/site approval state in the Simulator.");
1258 // get approval status for this app/domain pair
1259 SWCFlags flags
= _SecAppDomainApprovalStatus(appID
, fqdn
, error
);
1260 //approved = ((flags & kSWCFlag_SiteApproved) && (flags & kSWCFlag_UserApproved));
1261 if (!(flags
& kSWCFlag_SiteApproved
)) {
1266 // give ourselves access to see matching items for kSecSafariAccessGroup
1267 accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&accessGroup
, 1, &kCFTypeArrayCallBacks
);
1269 // create lookup query
1270 query
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1272 SecError(errSecAllocate
, error
, CFSTR("Unable to create query dictionary"));
1275 CFDictionaryAddValue(query
, kSecClass
, kSecClassInternetPassword
);
1276 CFDictionaryAddValue(query
, kSecAttrAccessGroup
, kSecSafariAccessGroup
);
1277 CFDictionaryAddValue(query
, kSecAttrAuthenticationType
, kSecAttrAuthenticationTypeHTMLForm
);
1278 CFDictionaryAddValue(query
, kSecAttrServer
, fqdn
);
1279 CFDictionaryAddValue(query
, kSecAttrSynchronizable
, kCFBooleanTrue
);
1281 // check for presence of Safari's negative entry ('passwords not saved')
1282 CFDictionarySetValue(query
, kSecAttrAccount
, kSecSafariPasswordsNotSaved
);
1283 ok
= _SecItemCopyMatching(query
, accessGroups
, result
, error
);
1284 CFReleaseNull(*result
);
1285 CFReleaseNull(*error
);
1287 SecError(errSecDuplicateItem
, error
, CFSTR("Item already exists for this server"));
1291 // now use the provided account (and optional port number, if one was present)
1292 CFDictionarySetValue(query
, kSecAttrAccount
, account
);
1293 if (port
< -1 || port
> 0) {
1294 SInt16 portValueShort
= (port
& 0xFFFF);
1295 CFNumberRef portNumber
= CFNumberCreate(NULL
, kCFNumberSInt16Type
, &portValueShort
);
1296 CFDictionaryAddValue(query
, kSecAttrPort
, portNumber
);
1297 CFReleaseSafe(portNumber
);
1300 // look up existing password
1301 if (_SecItemCopyMatching(query
, accessGroups
, result
, error
)) {
1302 // found it, so this becomes either an "update password" or "delete password" operation
1303 CFReleaseNull(*result
);
1304 CFReleaseNull(*error
);
1305 update
= (password
!= NULL
);
1307 attrs
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1308 CFDataRef credential
= CFStringCreateExternalRepresentation(kCFAllocatorDefault
, password
, kCFStringEncodingUTF8
, 0);
1309 CFDictionaryAddValue(attrs
, kSecValueData
, credential
);
1310 CFReleaseSafe(credential
);
1311 CFDictionaryAddValue(attrs
, kSecAttrComment
, kSecSafariDefaultComment
);
1313 // confirm the update
1314 // (per rdar://16676310 we always prompt, even if there was prior user approval)
1315 ok
= /*approved ||*/ swca_confirm_operation(swca_update_request_id
, clientAuditToken
, query
, error
,
1316 ^void (CFStringRef fqdn
) { _SecAddNegativeWebCredential(fqdn
, appID
, false); });
1318 ok
= _SecItemUpdate(query
, attrs
, accessGroups
, error
);
1322 // confirm the delete
1323 // (per rdar://16676288 we always prompt, even if there was prior user approval)
1324 ok
= /*approved ||*/ swca_confirm_operation(swca_delete_request_id
, clientAuditToken
, query
, error
,
1325 ^void (CFStringRef fqdn
) { _SecAddNegativeWebCredential(fqdn
, appID
, false); });
1327 ok
= _SecItemDelete(query
, accessGroups
, error
);
1331 CFReleaseNull(*error
);
1335 CFReleaseNull(*result
);
1336 CFReleaseNull(*error
);
1338 // password does not exist, so prepare to add it
1340 // a NULL password value removes the existing credential. Since we didn't find it, this is a no-op.
1345 CFStringRef label
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%@ (%@)"), fqdn
, account
);
1347 CFDictionaryAddValue(query
, kSecAttrLabel
, label
);
1348 CFReleaseSafe(label
);
1350 // NOTE: we always expect to use HTTPS for web forms.
1351 CFDictionaryAddValue(query
, kSecAttrProtocol
, kSecAttrProtocolHTTPS
);
1353 CFDataRef credential
= CFStringCreateExternalRepresentation(kCFAllocatorDefault
, password
, kCFStringEncodingUTF8
, 0);
1354 CFDictionarySetValue(query
, kSecValueData
, credential
);
1355 CFReleaseSafe(credential
);
1356 CFDictionarySetValue(query
, kSecAttrComment
, kSecSafariDefaultComment
);
1358 CFReleaseSafe(accessGroups
);
1359 accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&kSecSafariAccessGroup
, 1, &kCFTypeArrayCallBacks
);
1361 // mark the item as created by this function
1362 const int32_t creator_value
= 'swca';
1363 CFNumberRef creator
= CFNumberCreate(kCFAllocatorDefault
, kCFNumberSInt32Type
, &creator_value
);
1365 CFDictionarySetValue(query
, kSecAttrCreator
, creator
);
1366 CFReleaseSafe(creator
);
1371 // (per rdar://16680019, we won't prompt here in the normal case)
1372 ok
= /*approved ||*/ swca_confirm_operation(swca_add_request_id
, clientAuditToken
, query
, error
,
1373 ^void (CFStringRef fqdn
) { _SecAddNegativeWebCredential(fqdn
, appID
, false); });
1377 ok
= _SecItemAdd(query
, accessGroups
, result
, error
);
1381 #if 0 /* debugging */
1383 const char *op_str
= (password
) ? ((update
) ? "updated" : "added") : "deleted";
1384 const char *result_str
= (ok
) ? "true" : "false";
1385 secerror("result=%s, %s item %@, error=%@", result_str
, op_str
, *result
, *error
);
1390 CFReleaseSafe(attrs
);
1391 CFReleaseSafe(query
);
1392 CFReleaseSafe(accessGroups
);
1393 CFReleaseSafe(fqdn
);
1397 /* Specialized version of SecItemCopyMatching for shared web credentials */
1399 _SecCopySharedWebCredential(CFDictionaryRef query
,
1400 const audit_token_t
*clientAuditToken
,
1404 CFErrorRef
*error
) {
1406 CFMutableArrayRef credentials
= NULL
;
1407 CFMutableArrayRef foundItems
= NULL
;
1408 CFMutableArrayRef fqdns
= NULL
;
1409 CFArrayRef accessGroups
= NULL
;
1410 CFStringRef fqdn
= NULL
;
1411 CFStringRef account
= NULL
;
1416 require_quiet(result
, cleanup
);
1417 credentials
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
1418 foundItems
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
1419 fqdns
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
1421 // give ourselves access to see matching items for kSecSafariAccessGroup
1422 CFStringRef accessGroup
= CFSTR("*");
1423 accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&accessGroup
, 1, &kCFTypeArrayCallBacks
);
1425 // On input, the query dictionary contains optional fqdn and account entries.
1426 fqdn
= CFDictionaryGetValue(query
, kSecAttrServer
);
1427 account
= CFDictionaryGetValue(query
, kSecAttrAccount
);
1429 // Check autofill enabled status
1430 if (!swca_autofill_enabled(clientAuditToken
)) {
1431 SecError(errSecBadReq
, error
, CFSTR("Autofill is not enabled in Safari settings"));
1435 // Check fqdn; if NULL, add domains from caller's entitlement.
1437 CFArrayAppendValue(fqdns
, fqdn
);
1440 CFIndex idx
, count
= CFArrayGetCount(domains
);
1441 for (idx
=0; idx
< count
; idx
++) {
1442 CFStringRef str
= (CFStringRef
) CFArrayGetValueAtIndex(domains
, idx
);
1443 // Parse the entry for our service label prefix
1444 if (str
&& CFStringHasPrefix(str
, kSecSharedWebCredentialsService
)) {
1445 CFIndex prefix_len
= CFStringGetLength(kSecSharedWebCredentialsService
)+1;
1446 CFIndex substr_len
= CFStringGetLength(str
) - prefix_len
;
1447 CFRange range
= { prefix_len
, substr_len
};
1448 fqdn
= CFStringCreateWithSubstring(kCFAllocatorDefault
, str
, range
);
1450 CFArrayAppendValue(fqdns
, fqdn
);
1456 count
= CFArrayGetCount(fqdns
);
1458 SecError(errSecParam
, error
, CFSTR("No domain provided"));
1462 // Aggregate search results for each domain
1463 for (idx
= 0; idx
< count
; idx
++) {
1464 CFMutableArrayRef items
= NULL
;
1465 CFMutableDictionaryRef attrs
= NULL
;
1466 fqdn
= (CFStringRef
) CFArrayGetValueAtIndex(fqdns
, idx
);
1470 // Parse the fqdn for a possible port specifier.
1472 CFStringRef urlStr
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%@%@"), kSecSharedCredentialUrlScheme
, fqdn
);
1474 CFURLRef url
= CFURLCreateWithString(kCFAllocatorDefault
, urlStr
, nil
);
1476 CFStringRef hostname
= CFURLCopyHostName(url
);
1478 CFReleaseSafe(fqdn
);
1480 port
= CFURLGetPortNumber(url
);
1484 CFReleaseSafe(urlStr
);
1488 #if TARGET_IPHONE_SIMULATOR
1489 secerror("app/site association entitlements not checked in Simulator");
1491 OSStatus status
= errSecMissingEntitlement
;
1493 SecError(status
, error
, CFSTR("Missing application-identifier entitlement"));
1494 CFReleaseSafe(fqdn
);
1497 // validate that fqdn is part of caller's entitlement
1498 if (_SecEntitlementContainsDomainForService(domains
, fqdn
, kSecSharedWebCredentialsService
)) {
1499 status
= errSecSuccess
;
1501 if (errSecSuccess
!= status
) {
1502 CFStringRef msg
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
,
1503 CFSTR("%@ not found in %@ entitlement"), fqdn
, kSecEntitlementAssociatedDomains
);
1505 msg
= CFRetain(CFSTR("Requested domain not found in entitlement"));
1507 SecError(status
, error
, CFSTR("%@"), msg
);
1509 CFReleaseSafe(fqdn
);
1514 attrs
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1516 SecError(errSecAllocate
, error
, CFSTR("Unable to create query dictionary"));
1517 CFReleaseSafe(fqdn
);
1520 CFDictionaryAddValue(attrs
, kSecClass
, kSecClassInternetPassword
);
1521 CFDictionaryAddValue(attrs
, kSecAttrAccessGroup
, kSecSafariAccessGroup
);
1522 CFDictionaryAddValue(attrs
, kSecAttrAuthenticationType
, kSecAttrAuthenticationTypeHTMLForm
);
1523 CFDictionaryAddValue(attrs
, kSecAttrServer
, fqdn
);
1525 CFDictionaryAddValue(attrs
, kSecAttrAccount
, account
);
1527 if (port
< -1 || port
> 0) {
1528 SInt16 portValueShort
= (port
& 0xFFFF);
1529 CFNumberRef portNumber
= CFNumberCreate(NULL
, kCFNumberSInt16Type
, &portValueShort
);
1530 CFDictionaryAddValue(attrs
, kSecAttrPort
, portNumber
);
1531 CFReleaseSafe(portNumber
);
1533 CFDictionaryAddValue(attrs
, kSecAttrSynchronizable
, kCFBooleanTrue
);
1534 CFDictionaryAddValue(attrs
, kSecMatchLimit
, kSecMatchLimitAll
);
1535 CFDictionaryAddValue(attrs
, kSecReturnAttributes
, kCFBooleanTrue
);
1536 CFDictionaryAddValue(attrs
, kSecReturnData
, kCFBooleanTrue
);
1538 ok
= _SecItemCopyMatching(attrs
, accessGroups
, (CFTypeRef
*)&items
, error
);
1540 // ignore interim error since we have multiple domains to search
1541 CFReleaseNull(*error
);
1543 if (ok
&& items
&& CFGetTypeID(items
) == CFArrayGetTypeID()) {
1544 #if TARGET_IPHONE_SIMULATOR
1545 secerror("Ignoring app/site approval state in the Simulator.");
1546 bool approved
= true;
1548 // get approval status for this app/domain pair
1549 SWCFlags flags
= _SecAppDomainApprovalStatus(appID
, fqdn
, error
);
1551 // ignore interim error since we have multiple domains to check
1552 CFReleaseNull(*error
);
1554 bool approved
= (flags
& kSWCFlag_SiteApproved
);
1557 CFArrayAppendArray(foundItems
, items
, CFRangeMake(0, CFArrayGetCount(items
)));
1560 CFReleaseSafe(items
);
1561 CFReleaseSafe(attrs
);
1562 CFReleaseSafe(fqdn
);
1565 // If matching credentials are found, the credentials provided to the completionHandler
1566 // will be a CFArrayRef containing CFDictionaryRef entries. Each dictionary entry will
1567 // contain the following pairs (see Security/SecItem.h):
1568 // key: kSecAttrServer value: CFStringRef (the website)
1569 // key: kSecAttrAccount value: CFStringRef (the account)
1570 // key: kSecSharedPassword value: CFStringRef (the password)
1572 // key: kSecAttrPort value: CFNumberRef (the port number, if non-standard for https)
1574 count
= CFArrayGetCount(foundItems
);
1575 for (idx
= 0; idx
< count
; idx
++) {
1576 CFDictionaryRef dict
= (CFDictionaryRef
) CFArrayGetValueAtIndex(foundItems
, idx
);
1577 CFMutableDictionaryRef newdict
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1578 if (newdict
&& dict
&& CFGetTypeID(dict
) == CFDictionaryGetTypeID()) {
1579 CFStringRef srvr
= CFDictionaryGetValue(dict
, kSecAttrServer
);
1580 CFStringRef acct
= CFDictionaryGetValue(dict
, kSecAttrAccount
);
1581 CFNumberRef pnum
= CFDictionaryGetValue(dict
, kSecAttrPort
);
1582 CFStringRef icmt
= CFDictionaryGetValue(dict
, kSecAttrComment
);
1583 CFDataRef data
= CFDictionaryGetValue(dict
, kSecValueData
);
1585 CFDictionaryAddValue(newdict
, kSecAttrServer
, srvr
);
1588 CFDictionaryAddValue(newdict
, kSecAttrAccount
, acct
);
1592 if (CFNumberGetValue(pnum
, kCFNumberSInt16Type
, &pval
) &&
1593 (pval
< -1 || pval
> 0)) {
1594 CFDictionaryAddValue(newdict
, kSecAttrPort
, pnum
);
1598 CFStringRef password
= CFStringCreateFromExternalRepresentation(kCFAllocatorDefault
, data
, kCFStringEncodingUTF8
);
1600 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1601 CFDictionaryAddValue(newdict
, kSecSharedPassword
, password
);
1603 CFDictionaryAddValue(newdict
, CFSTR("spwd"), password
);
1605 CFReleaseSafe(password
);
1608 if (icmt
&& CFEqual(icmt
, kSecSafariDefaultComment
)) {
1609 CFArrayInsertValueAtIndex(credentials
, 0, newdict
);
1611 CFArrayAppendValue(credentials
, newdict
);
1614 CFReleaseSafe(newdict
);
1621 // create a new array of dictionaries (without the actual password) for picker UI
1622 count
= CFArrayGetCount(credentials
);
1623 CFMutableArrayRef items
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
1624 for (idx
= 0; idx
< count
; idx
++) {
1625 CFDictionaryRef dict
= (CFDictionaryRef
) CFArrayGetValueAtIndex(credentials
, idx
);
1626 CFMutableDictionaryRef newdict
= CFDictionaryCreateMutableCopy(kCFAllocatorDefault
, 0, dict
);
1627 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1628 CFDictionaryRemoveValue(newdict
, kSecSharedPassword
);
1630 CFDictionaryRemoveValue(newdict
, CFSTR("spwd"));
1632 CFArrayAppendValue(items
, newdict
);
1633 CFReleaseSafe(newdict
);
1636 // prompt user to select one of the dictionary items
1637 CFDictionaryRef selected
= swca_copy_selected_dictionary(swca_select_request_id
,
1638 clientAuditToken
, items
, error
);
1640 // find the matching item in our credentials array
1641 CFStringRef srvr
= CFDictionaryGetValue(selected
, kSecAttrServer
);
1642 CFStringRef acct
= CFDictionaryGetValue(selected
, kSecAttrAccount
);
1643 CFNumberRef pnum
= CFDictionaryGetValue(selected
, kSecAttrPort
);
1644 for (idx
= 0; idx
< count
; idx
++) {
1645 CFDictionaryRef dict
= (CFDictionaryRef
) CFArrayGetValueAtIndex(credentials
, idx
);
1646 CFStringRef srvr1
= CFDictionaryGetValue(dict
, kSecAttrServer
);
1647 CFStringRef acct1
= CFDictionaryGetValue(dict
, kSecAttrAccount
);
1648 CFNumberRef pnum1
= CFDictionaryGetValue(dict
, kSecAttrPort
);
1650 if (!srvr
|| !srvr1
|| !CFEqual(srvr
, srvr1
)) continue;
1651 if (!acct
|| !acct1
|| !CFEqual(acct
, acct1
)) continue;
1652 if ((pnum
&& pnum1
) && !CFEqual(pnum
, pnum1
)) continue;
1655 CFReleaseSafe(selected
);
1662 CFReleaseSafe(items
);
1663 CFArrayRemoveAllValues(credentials
);
1664 if (selected
&& ok
) {
1665 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1666 fqdn
= CFDictionaryGetValue(selected
, kSecAttrServer
);
1668 CFArrayAppendValue(credentials
, selected
);
1672 // confirm the access
1673 ok
= swca_confirm_operation(swca_copy_request_id
, clientAuditToken
, query
, error
,
1674 ^void (CFStringRef fqdn
) { _SecAddNegativeWebCredential(fqdn
, appID
, false); });
1677 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1678 // register confirmation with database
1679 OSStatus status
= _SecSWCEnsuredInitialized();
1681 SecError(status
, error
, CFSTR("SWC initialize failed"));
1683 CFReleaseSafe(selected
);
1686 CFRetainSafe(appID
);
1688 if (0 != sSWCSetServiceFlags_f(kSecSharedWebCredentialsService
,
1689 appID
, fqdn
, kSWCFlag_ExternalMask
, kSWCFlag_UserApproved
,
1690 ^void(OSStatus inStatus
, SWCFlags inNewFlags
){
1691 CFReleaseSafe(appID
);
1692 CFReleaseSafe(fqdn
);
1695 // we didn't queue the block
1696 CFReleaseSafe(appID
);
1697 CFReleaseSafe(fqdn
);
1701 CFReleaseSafe(selected
);
1703 else if (NULL
== *error
) {
1704 // found no items, and we haven't already filled in the error
1705 SecError(errSecItemNotFound
, error
, CFSTR("no matching items found"));
1710 CFArrayRemoveAllValues(credentials
);
1712 CFReleaseSafe(foundItems
);
1713 *result
= credentials
;
1714 CFReleaseSafe(accessGroups
);
1715 CFReleaseSafe(fqdns
);
1716 #if 0 /* debugging */
1717 secerror("result=%s, copied items %@, error=%@", (ok
) ? "true" : "false", *result
, *error
);
1723 // MARK: Keychain backup
1725 CF_RETURNS_RETAINED CFDataRef
1726 _SecServerKeychainBackup(CFDataRef keybag
, CFDataRef passcode
, CFErrorRef
*error
) {
1728 SecDbConnectionRef dbt
= SecDbConnectionAquire(kc_dbhandle(), false, error
);
1733 if (keybag
== NULL
&& passcode
== NULL
) {
1735 backup
= SecServerExportBackupableKeychain(dbt
, KEYBAG_DEVICE
, backup_keybag_handle
, error
);
1736 #else /* !USE_KEYSTORE */
1737 SecError(errSecParam
, error
, CFSTR("Why are you doing this?"));
1739 #endif /* USE_KEYSTORE */
1741 backup
= SecServerKeychainBackup(dbt
, keybag
, passcode
, error
);
1744 SecDbConnectionRelease(dbt
);
1750 _SecServerKeychainRestore(CFDataRef backup
, CFDataRef keybag
, CFDataRef passcode
, CFErrorRef
*error
) {
1751 if (backup
== NULL
|| keybag
== NULL
)
1752 return SecError(errSecParam
, error
, CFSTR("backup or keybag missing"));
1754 __block
bool ok
= true;
1755 ok
&= SecDbPerformWrite(kc_dbhandle(), error
, ^(SecDbConnectionRef dbconn
) {
1756 ok
= SecServerKeychainRestore(dbconn
, backup
, keybag
, passcode
, error
);
1760 SecKeychainChanged(true);
1768 // MARK: SecItemDataSource
1770 // Make sure to call this before any writes to the keychain, so that we fire
1771 // up the engines to monitor manifest changes.
1772 SOSDataSourceFactoryRef
SecItemDataSourceFactoryGetDefault(void) {
1773 return SecItemDataSourceFactoryGetShared(kc_dbhandle());
1776 /* AUDIT[securityd]:
1777 args_in (ok) is a caller provided, CFDictionaryRef.
1780 CF_RETURNS_RETAINED CFArrayRef
1781 _SecServerKeychainSyncUpdateMessage(CFDictionaryRef updates
, CFErrorRef
*error
) {
1782 // This never fails, trust us!
1783 return SOSCCHandleUpdateMessage(updates
);
1787 // Truthiness in the cloud backup/restore support.
1790 static CFDictionaryRef
1791 _SecServerCopyTruthInTheCloud(CFDataRef keybag
, CFDataRef password
,
1792 CFDictionaryRef backup
, CFErrorRef
*error
)
1794 SOSManifestRef mold
= NULL
, mnow
= NULL
, mdelete
= NULL
, madd
= NULL
;
1795 __block CFMutableDictionaryRef backup_new
= NULL
;
1796 keybag_handle_t bag_handle
;
1797 if (!ks_open_keybag(keybag
, password
, &bag_handle
, error
))
1800 // We need to have a datasource singleton for protection domain
1801 // kSecAttrAccessibleWhenUnlocked and keep a single shared engine
1802 // instance around which we create in the datasource constructor as well.
1803 SOSDataSourceFactoryRef dsf
= SecItemDataSourceFactoryGetDefault();
1804 SOSDataSourceRef ds
= SOSDataSourceFactoryCreateDataSource(dsf
, kSecAttrAccessibleWhenUnlocked
, error
);
1806 backup_new
= backup
? CFDictionaryCreateMutableCopy(kCFAllocatorDefault
, 0, backup
) : CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1807 mold
= SOSCreateManifestWithBackup(backup
, error
);
1808 SOSEngineRef engine
= SOSDataSourceGetSharedEngine(ds
, error
);
1809 mnow
= SOSEngineCopyManifest(engine
, NULL
);
1811 mnow
= SOSDataSourceCopyManifestWithViewNameSet(ds
, SOSViewsGetV0ViewSet(), error
);
1814 CFReleaseNull(backup_new
);
1815 secerror("failed to obtain manifest for keychain: %@", error
? *error
: NULL
);
1817 SOSManifestDiff(mold
, mnow
, &mdelete
, &madd
, error
);
1820 // Delete everything from the new_backup that is no longer in the datasource according to the datasources manifest.
1821 SOSManifestForEach(mdelete
, ^(CFDataRef digest_data
, bool *stop
) {
1822 CFStringRef deleted_item_key
= CFDataCopyHexString(digest_data
);
1823 CFDictionaryRemoveValue(backup_new
, deleted_item_key
);
1824 CFRelease(deleted_item_key
);
1827 CFMutableArrayRef changes
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
1828 SOSDataSourceForEachObject(ds
, madd
, error
, ^void(CFDataRef digest
, SOSObjectRef object
, bool *stop
) {
1829 CFErrorRef localError
= NULL
;
1830 CFDataRef digest_data
= NULL
;
1831 CFTypeRef value
= NULL
;
1833 // Key in our manifest can't be found in db, remove it from our manifest
1834 SOSChangesAppendDelete(changes
, digest
);
1835 } else if (!(digest_data
= SOSObjectCopyDigest(ds
, object
, &localError
))
1836 || !(value
= SOSObjectCopyBackup(ds
, object
, bag_handle
, &localError
))) {
1837 if (SecErrorGetOSStatus(localError
) == errSecDecode
) {
1838 // Ignore decode errors, pretend the objects aren't there
1839 CFRelease(localError
);
1840 // Object undecodable, remove it from our manifest
1841 SOSChangesAppendDelete(changes
, digest
);
1843 // Stop iterating and propagate out all other errors.
1845 *error
= localError
;
1846 CFReleaseNull(backup_new
);
1849 // TODO: Should we skip tombstones here?
1850 CFStringRef key
= CFDataCopyHexString(digest_data
);
1851 CFDictionarySetValue(backup_new
, key
, value
);
1854 CFReleaseSafe(digest_data
);
1855 CFReleaseSafe(value
);
1856 }) || CFReleaseNull(backup_new
);
1858 if (CFArrayGetCount(changes
)) {
1859 if (!SOSEngineUpdateChanges(engine
, kSOSDataSourceSOSTransaction
, changes
, error
)) {
1860 CFReleaseNull(backup_new
);
1863 CFReleaseSafe(changes
);
1865 SOSDataSourceRelease(ds
, error
) || CFReleaseNull(backup_new
);
1868 CFReleaseSafe(mold
);
1869 CFReleaseSafe(mnow
);
1870 CFReleaseSafe(madd
);
1871 CFReleaseSafe(mdelete
);
1872 ks_close_keybag(bag_handle
, error
) || CFReleaseNull(backup_new
);
1878 _SecServerRestoreTruthInTheCloud(CFDataRef keybag
, CFDataRef password
, CFDictionaryRef backup_in
, CFErrorRef
*error
) {
1879 __block
bool ok
= true;
1880 keybag_handle_t bag_handle
;
1881 if (!ks_open_keybag(keybag
, password
, &bag_handle
, error
))
1884 SOSManifestRef mbackup
= SOSCreateManifestWithBackup(backup_in
, error
);
1886 SOSDataSourceFactoryRef dsf
= SecItemDataSourceFactoryGetDefault();
1887 SOSDataSourceRef ds
= SOSDataSourceFactoryCreateDataSource(dsf
, kSecAttrAccessibleWhenUnlocked
, error
);
1888 ok
&= ds
&& SOSDataSourceWith(ds
, error
, ^(SOSTransactionRef txn
, bool *commit
) {
1889 SOSManifestRef mnow
= SOSDataSourceCopyManifestWithViewNameSet(ds
, SOSViewsGetV0BackupViewSet(), error
);
1890 SOSManifestRef mdelete
= NULL
, madd
= NULL
;
1891 SOSManifestDiff(mnow
, mbackup
, &mdelete
, &madd
, error
);
1893 // Don't delete everything in datasource not in backup.
1895 // Add items from the backup
1896 SOSManifestForEach(madd
, ^void(CFDataRef e
, bool *stop
) {
1897 CFDictionaryRef item
= NULL
;
1898 CFStringRef sha1
= CFDataCopyHexString(e
);
1900 item
= CFDictionaryGetValue(backup_in
, sha1
);
1904 CFErrorRef localError
= NULL
;
1906 if (!SOSObjectRestoreObject(ds
, txn
, bag_handle
, item
, &localError
)) {
1907 OSStatus status
= SecErrorGetOSStatus(localError
);
1908 if (status
== errSecDuplicateItem
) {
1909 // Log and ignore duplicate item errors during restore
1910 secnotice("titc", "restore %@ not replacing existing item", item
);
1911 } else if (status
== errSecDecode
) {
1912 // Log and ignore corrupted item errors during restore
1913 secnotice("titc", "restore %@ skipping corrupted item %@", item
, localError
);
1915 if (status
== errSecInteractionNotAllowed
)
1917 // Propagate the first other error upwards (causing the restore to fail).
1918 secerror("restore %@ failed %@", item
, localError
);
1920 if (error
&& !*error
) {
1921 *error
= localError
;
1925 CFReleaseSafe(localError
);
1929 ok
&= SOSDataSourceRelease(ds
, error
);
1930 CFReleaseNull(mdelete
);
1931 CFReleaseNull(madd
);
1932 CFReleaseNull(mnow
);
1937 ok
&= ks_close_keybag(bag_handle
, error
);
1943 CF_RETURNS_RETAINED CFDictionaryRef
1944 _SecServerBackupSyncable(CFDictionaryRef backup
, CFDataRef keybag
, CFDataRef password
, CFErrorRef
*error
) {
1945 require_action_quiet(isData(keybag
), errOut
, SecError(errSecParam
, error
, CFSTR("keybag %@ not a data"), keybag
));
1946 require_action_quiet(!backup
|| isDictionary(backup
), errOut
, SecError(errSecParam
, error
, CFSTR("backup %@ not a dictionary"), backup
));
1947 require_action_quiet(!password
|| isData(password
), errOut
, SecError(errSecParam
, error
, CFSTR("password %@ not a data"), password
));
1949 return _SecServerCopyTruthInTheCloud(keybag
, password
, backup
, error
);
1956 _SecServerRestoreSyncable(CFDictionaryRef backup
, CFDataRef keybag
, CFDataRef password
, CFErrorRef
*error
) {
1958 require_action_quiet(isData(keybag
), errOut
, ok
= SecError(errSecParam
, error
, CFSTR("keybag %@ not a data"), keybag
));
1959 require_action_quiet(isDictionary(backup
), errOut
, ok
= SecError(errSecParam
, error
, CFSTR("backup %@ not a dictionary"), backup
));
1961 require_action_quiet(isData(password
), errOut
, ok
= SecError(errSecParam
, error
, CFSTR("password not a data")));
1964 ok
= _SecServerRestoreTruthInTheCloud(keybag
, password
, backup
, error
);
1970 bool _SecServerRollKeys(bool force
, CFErrorRef
*error
) {
1972 uint32_t keystore_generation_status
= 0;
1973 if (aks_generation(KEYBAG_DEVICE
, generation_noop
, &keystore_generation_status
))
1975 uint32_t current_generation
= keystore_generation_status
& generation_current
;
1977 return kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
1978 bool up_to_date
= s3dl_dbt_keys_current(dbt
, current_generation
, NULL
);
1980 if (force
&& !up_to_date
) {
1981 up_to_date
= s3dl_dbt_update_keys(dbt
, error
);
1983 secerror("Completed roll keys.");
1984 up_to_date
= s3dl_dbt_keys_current(dbt
, current_generation
, NULL
);
1987 secerror("Failed to roll keys.");