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 void (*SecTaskDiagnoseEntitlements
)(CFArrayRef accessGroups
) = NULL
;
716 /* AUDIT[securityd](done):
717 query (ok) is a caller provided dictionary, only its cf type has been checked.
720 SecItemServerCopyMatching(CFDictionaryRef query
, CFTypeRef
*result
,
721 CFArrayRef accessGroups
, CFErrorRef
*error
)
724 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
))) {
725 if (SecTaskDiagnoseEntitlements
)
726 SecTaskDiagnoseEntitlements(accessGroups
);
727 return SecError(errSecMissingEntitlement
, error
,
728 CFSTR("client has neither application-identifier nor keychain-access-groups entitlements"));
731 if (CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), CFSTR("*"))) {
732 /* Having the special accessGroup "*" allows access to all accessGroups. */
737 Query
*q
= query_create_with_limit(query
, 1, error
);
739 CFStringRef agrp
= CFDictionaryGetValue(q
->q_item
, kSecAttrAccessGroup
);
740 if (agrp
&& accessGroupsAllows(accessGroups
, agrp
)) {
741 // TODO: Return an error if agrp is not NULL and accessGroupsAllows() fails above.
742 const void *val
= agrp
;
743 accessGroups
= CFArrayCreate(0, &val
, 1, &kCFTypeArrayCallBacks
);
745 CFRetainSafe(accessGroups
);
748 query_set_caller_access_groups(q
, accessGroups
);
750 /* Sanity check the query. */
751 if (q
->q_use_item_list
) {
752 ok
= SecError(errSecUseItemListUnsupported
, error
, CFSTR("use item list unsupported"));
753 #if defined(MULTIPLE_KEYCHAINS)
754 } else if (q
->q_use_keychain
) {
755 ok
= SecError(errSecUseKeychainUnsupported
, error
, CFSTR("use keychain list unsupported"));
757 } else if (q
->q_match_issuer
&& ((q
->q_class
!= &cert_class
) &&
758 (q
->q_class
!= &identity_class
))) {
759 ok
= SecError(errSecUnsupportedOperation
, error
, CFSTR("unsupported match attribute"));
760 } else if (q
->q_return_type
!= 0 && result
== NULL
) {
761 ok
= SecError(errSecReturnMissingPointer
, error
, CFSTR("missing pointer"));
762 } else if (!q
->q_error
) {
763 ok
= kc_with_dbt(false, error
, ^(SecDbConnectionRef dbt
) {
764 return s3dl_copy_matching(dbt
, q
, result
, accessGroups
, error
);
768 CFReleaseSafe(accessGroups
);
769 if (!query_destroy(q
, error
))
777 _SecItemCopyMatching(CFDictionaryRef query
, CFArrayRef accessGroups
, CFTypeRef
*result
, CFErrorRef
*error
) {
778 return SecItemServerCopyMatching(query
, result
, accessGroups
, error
);
781 /* AUDIT[securityd](done):
782 attributes (ok) is a caller provided dictionary, only its cf type has
786 _SecItemAdd(CFDictionaryRef attributes
, CFArrayRef accessGroups
,
787 CFTypeRef
*result
, CFErrorRef
*error
)
791 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
))) {
792 if (SecTaskDiagnoseEntitlements
)
793 SecTaskDiagnoseEntitlements(accessGroups
);
794 return SecError(errSecMissingEntitlement
, error
,
795 CFSTR("client has neither application-identifier nor keychain-access-groups entitlements"));
798 Query
*q
= query_create_with_limit(attributes
, 0, error
);
800 /* Access group sanity checking. */
801 CFStringRef agrp
= (CFStringRef
)CFDictionaryGetValue(attributes
,
802 kSecAttrAccessGroup
);
804 CFArrayRef ag
= accessGroups
;
805 /* Having the special accessGroup "*" allows access to all accessGroups. */
806 if (CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), CFSTR("*")))
810 /* The user specified an explicit access group, validate it. */
811 if (!accessGroupsAllows(accessGroups
, agrp
))
812 ok
= SecError(errSecNoAccessForItem
, error
, CFSTR("NoAccessForItem"));
814 agrp
= (CFStringRef
)CFArrayGetValueAtIndex(ag
, 0);
816 /* We are using an implicit access group, add it as if the user
817 specified it as an attribute. */
818 query_add_attribute(kSecAttrAccessGroup
, agrp
, q
);
822 query_ensure_access_control(q
, agrp
);
825 ok
= SecError(errSecValuePersistentRefUnsupported
, error
, CFSTR("q_row_id")); // TODO: better error string
826 #if defined(MULTIPLE_KEYCHAINS)
827 else if (q
->q_use_keychain_list
)
828 ok
= SecError(errSecUseKeychainListUnsupported
, error
, CFSTR("q_use_keychain_list")); // TODO: better error string;
830 else if (!q
->q_error
) {
831 ok
= kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
){
832 return kc_transaction(dbt
, error
, ^{
833 query_pre_add(q
, true);
834 return s3dl_query_add(dbt
, q
, result
, error
);
839 ok
= query_notify_and_destroy(q
, ok
, error
);
846 /* AUDIT[securityd](done):
847 query (ok) and attributesToUpdate (ok) are a caller provided dictionaries,
848 only their cf types have been checked.
851 _SecItemUpdate(CFDictionaryRef query
, CFDictionaryRef attributesToUpdate
,
852 CFArrayRef accessGroups
, CFErrorRef
*error
)
855 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
))) {
856 if (SecTaskDiagnoseEntitlements
)
857 SecTaskDiagnoseEntitlements(accessGroups
);
858 return SecError(errSecMissingEntitlement
, error
,
859 CFSTR("client has neither application-identifier nor keychain-access-groups entitlements"));
862 if (CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), CFSTR("*"))) {
863 /* Having the special accessGroup "*" allows access to all accessGroups. */
868 Query
*q
= query_create_with_limit(query
, kSecMatchUnlimited
, error
);
873 /* Sanity check the query. */
874 query_set_caller_access_groups(q
, accessGroups
);
875 if (q
->q_use_item_list
) {
876 ok
= SecError(errSecUseItemListUnsupported
, error
, CFSTR("use item list not supported"));
877 } else if (q
->q_return_type
& kSecReturnDataMask
) {
878 /* Update doesn't return anything so don't ask for it. */
879 ok
= SecError(errSecReturnDataUnsupported
, error
, CFSTR("return data not supported by update"));
880 } else if (q
->q_return_type
& kSecReturnAttributesMask
) {
881 ok
= SecError(errSecReturnAttributesUnsupported
, error
, CFSTR("return attributes not supported by update"));
882 } else if (q
->q_return_type
& kSecReturnRefMask
) {
883 ok
= SecError(errSecReturnRefUnsupported
, error
, CFSTR("return ref not supported by update"));
884 } else if (q
->q_return_type
& kSecReturnPersistentRefMask
) {
885 ok
= SecError(errSecReturnPersistentRefUnsupported
, error
, CFSTR("return persistent ref not supported by update"));
887 /* Access group sanity checking. */
888 CFStringRef agrp
= (CFStringRef
)CFDictionaryGetValue(attributesToUpdate
,
889 kSecAttrAccessGroup
);
891 /* The user is attempting to modify the access group column,
892 validate it to make sure the new value is allowable. */
893 if (!accessGroupsAllows(accessGroups
, agrp
)) {
894 ok
= SecError(errSecNoAccessForItem
, error
, CFSTR("accessGroup %@ not in %@"), agrp
, accessGroups
);
900 ok
= kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
901 return s3dl_query_update(dbt
, q
, attributesToUpdate
, accessGroups
, error
);
905 ok
= query_notify_and_destroy(q
, ok
, error
);
911 /* AUDIT[securityd](done):
912 query (ok) is a caller provided dictionary, only its cf type has been checked.
915 _SecItemDelete(CFDictionaryRef query
, CFArrayRef accessGroups
, CFErrorRef
*error
)
918 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
))) {
919 if (SecTaskDiagnoseEntitlements
)
920 SecTaskDiagnoseEntitlements(accessGroups
);
921 return SecError(errSecMissingEntitlement
, error
,
922 CFSTR("client has neither application-identifier nor keychain-access-groups entitlements"));
925 if (CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), CFSTR("*"))) {
926 /* Having the special accessGroup "*" allows access to all accessGroups. */
930 Query
*q
= query_create_with_limit(query
, kSecMatchUnlimited
, error
);
933 query_set_caller_access_groups(q
, accessGroups
);
934 /* Sanity check the query. */
935 if (q
->q_limit
!= kSecMatchUnlimited
)
936 ok
= SecError(errSecMatchLimitUnsupported
, error
, CFSTR("match limit not supported by delete"));
937 else if (query_match_count(q
) != 0)
938 ok
= SecError(errSecItemMatchUnsupported
, error
, CFSTR("match not supported by delete"));
940 ok
= SecError(errSecValueRefUnsupported
, error
, CFSTR("value ref not supported by delete"));
941 else if (q
->q_row_id
&& query_attr_count(q
))
942 ok
= SecError(errSecItemIllegalQuery
, error
, CFSTR("rowid and other attributes are mutually exclusive"));
944 ok
= kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
945 return s3dl_query_delete(dbt
, q
, accessGroups
, error
);
948 ok
= query_notify_and_destroy(q
, ok
, error
);
956 /* AUDIT[securityd](done):
957 No caller provided inputs.
960 SecItemServerDeleteAll(CFErrorRef
*error
) {
961 return kc_with_dbt(true, error
, ^bool (SecDbConnectionRef dbt
) {
962 return (kc_transaction(dbt
, error
, ^bool {
963 return (SecDbExec(dbt
, CFSTR("DELETE from genp;"), error
) &&
964 SecDbExec(dbt
, CFSTR("DELETE from inet;"), error
) &&
965 SecDbExec(dbt
, CFSTR("DELETE from cert;"), error
) &&
966 SecDbExec(dbt
, CFSTR("DELETE from keys;"), error
));
967 }) && SecDbExec(dbt
, CFSTR("VACUUM;"), error
));
972 _SecItemDeleteAll(CFErrorRef
*error
) {
973 return SecItemServerDeleteAll(error
);
978 // MARK: Shared web credentials
981 #define SEC_CONST_DECL(k,v) const CFStringRef k = CFSTR(v);
983 SEC_CONST_DECL (kSecSafariAccessGroup
, "com.apple.cfnetwork");
984 SEC_CONST_DECL (kSecSafariDefaultComment
, "default");
985 SEC_CONST_DECL (kSecSafariPasswordsNotSaved
, "Passwords not saved");
986 SEC_CONST_DECL (kSecSharedCredentialUrlScheme
, "https://");
987 SEC_CONST_DECL (kSecSharedWebCredentialsService
, "webcredentials");
989 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
990 static dispatch_once_t sSecSWCInitializeOnce
= 0;
991 static void * sSecSWCLibrary
= NULL
;
992 static SWCCheckService_f sSWCCheckService_f
= NULL
;
993 static SWCSetServiceFlags_f sSWCSetServiceFlags_f
= NULL
;
995 static OSStatus
_SecSWCEnsuredInitialized(void);
997 static OSStatus
_SecSWCEnsuredInitialized(void)
999 __block OSStatus status
= errSecNotAvailable
;
1001 dispatch_once(&sSecSWCInitializeOnce
, ^{
1002 sSecSWCLibrary
= dlopen("/System/Library/PrivateFrameworks/SharedWebCredentials.framework/SharedWebCredentials", RTLD_LAZY
| RTLD_LOCAL
);
1003 assert(sSecSWCLibrary
);
1004 if (sSecSWCLibrary
) {
1005 sSWCCheckService_f
= (SWCCheckService_f
)(uintptr_t) dlsym(sSecSWCLibrary
, "SWCCheckService");
1006 sSWCSetServiceFlags_f
= (SWCSetServiceFlags_f
)(uintptr_t) dlsym(sSecSWCLibrary
, "SWCSetServiceFlags");
1010 if (sSWCCheckService_f
&& sSWCSetServiceFlags_f
) {
1017 #if !TARGET_IPHONE_SIMULATOR
1019 _SecAppDomainApprovalStatus(CFStringRef appID
, CFStringRef fqdn
, CFErrorRef
*error
)
1021 __block SWCFlags flags
= kSWCFlags_None
;
1023 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1024 OSStatus status
= _SecSWCEnsuredInitialized();
1026 SecError(status
, error
, CFSTR("SWC initialize failed"));
1029 CFRetainSafe(appID
);
1031 dispatch_semaphore_t semaphore
= dispatch_semaphore_create(0);
1032 dispatch_retain(semaphore
);
1033 if (0 == sSWCCheckService_f(kSecSharedWebCredentialsService
, appID
, fqdn
,
1034 ^void (OSStatus inStatus
, SWCFlags inFlags
, CFDictionaryRef inDetails
) {
1035 if (!inStatus
) { flags
= inFlags
; }
1036 CFReleaseSafe(appID
);
1037 CFReleaseSafe(fqdn
);
1038 dispatch_semaphore_signal(semaphore
);
1039 dispatch_release(semaphore
);
1040 //secerror("SWCCheckService: inStatus=%d, flags=%0X", inStatus, flags);
1043 // wait for the block to complete, as we need its answer
1044 dispatch_semaphore_wait(semaphore
, DISPATCH_TIME_FOREVER
);
1046 else // didn't queue the block
1048 CFReleaseSafe(appID
);
1049 CFReleaseSafe(fqdn
);
1050 dispatch_release(semaphore
);
1052 dispatch_release(semaphore
);
1054 flags
|= (kSWCFlag_SiteApproved
);
1057 if (!error
) { return flags
; }
1060 // check website approval status
1061 if (!(flags
& kSWCFlag_SiteApproved
)) {
1062 if (flags
& kSWCFlag_Pending
) {
1063 SecError(errSecAuthFailed
, error
, CFSTR("Approval is pending for \"%@\", try later"), fqdn
);
1065 SecError(errSecAuthFailed
, error
, CFSTR("\"%@\" failed to approve \"%@\""), fqdn
, appID
);
1070 // check user approval status
1071 if (flags
& kSWCFlag_UserDenied
) {
1072 SecError(errSecAuthFailed
, error
, CFSTR("User denied access to \"%@\" by \"%@\""), fqdn
, appID
);
1078 #if !TARGET_IPHONE_SIMULATOR
1080 _SecEntitlementContainsDomainForService(CFArrayRef domains
, CFStringRef domain
, CFStringRef service
)
1082 bool result
= false;
1083 CFIndex idx
, count
= (domains
) ? CFArrayGetCount(domains
) : (CFIndex
) 0;
1084 if (!count
|| !domain
|| !service
) {
1087 for (idx
=0; idx
< count
; idx
++) {
1088 CFStringRef str
= (CFStringRef
) CFArrayGetValueAtIndex(domains
, idx
);
1089 if (str
&& CFStringHasPrefix(str
, kSecSharedWebCredentialsService
)) {
1090 CFIndex prefix_len
= CFStringGetLength(kSecSharedWebCredentialsService
)+1;
1091 CFIndex substr_len
= CFStringGetLength(str
) - prefix_len
;
1092 CFRange range
= { prefix_len
, substr_len
};
1093 CFStringRef substr
= CFStringCreateWithSubstring(kCFAllocatorDefault
, str
, range
);
1094 if (substr
&& CFEqual(substr
, domain
)) {
1097 CFReleaseSafe(substr
);
1108 _SecAddNegativeWebCredential(CFStringRef fqdn
, CFStringRef appID
, bool forSafari
)
1110 bool result
= false;
1111 if (!fqdn
) { return result
; }
1113 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1114 OSStatus status
= _SecSWCEnsuredInitialized();
1115 if (status
) { return false; }
1117 // update our database
1118 CFRetainSafe(appID
);
1120 if (0 == sSWCSetServiceFlags_f(kSecSharedWebCredentialsService
,
1121 appID
, fqdn
, kSWCFlag_ExternalMask
, kSWCFlag_UserDenied
,
1122 ^void(OSStatus inStatus
, SWCFlags inNewFlags
){
1123 CFReleaseSafe(appID
);
1124 CFReleaseSafe(fqdn
);
1129 else // didn't queue the block
1131 CFReleaseSafe(appID
);
1132 CFReleaseSafe(fqdn
);
1135 if (!forSafari
) { return result
; }
1137 // below this point: create a negative Safari web credential item
1139 CFMutableDictionaryRef attrs
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1140 if (!attrs
) { return result
; }
1142 CFErrorRef error
= NULL
;
1143 CFStringRef accessGroup
= CFSTR("*");
1144 CFArrayRef accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&accessGroup
, 1, &kCFTypeArrayCallBacks
);
1146 CFDictionaryAddValue(attrs
, kSecClass
, kSecClassInternetPassword
);
1147 CFDictionaryAddValue(attrs
, kSecAttrAccessGroup
, kSecSafariAccessGroup
);
1148 CFDictionaryAddValue(attrs
, kSecAttrAuthenticationType
, kSecAttrAuthenticationTypeHTMLForm
);
1149 CFDictionaryAddValue(attrs
, kSecAttrProtocol
, kSecAttrProtocolHTTPS
);
1150 CFDictionaryAddValue(attrs
, kSecAttrServer
, fqdn
);
1151 CFDictionaryAddValue(attrs
, kSecAttrSynchronizable
, kCFBooleanTrue
);
1153 (void)_SecItemDelete(attrs
, accessGroups
, &error
);
1154 CFReleaseNull(error
);
1156 CFDictionaryAddValue(attrs
, kSecAttrAccount
, kSecSafariPasswordsNotSaved
);
1157 CFDictionaryAddValue(attrs
, kSecAttrComment
, kSecSafariDefaultComment
);
1159 CFStringRef label
= CFStringCreateWithFormat(kCFAllocatorDefault
,
1160 NULL
, CFSTR("%@ (%@)"), fqdn
, kSecSafariPasswordsNotSaved
);
1162 CFDictionaryAddValue(attrs
, kSecAttrLabel
, label
);
1163 CFReleaseSafe(label
);
1167 CFDataRef data
= CFDataCreate(kCFAllocatorDefault
, &space
, 1);
1169 CFDictionarySetValue(attrs
, kSecValueData
, data
);
1170 CFReleaseSafe(data
);
1173 CFTypeRef addResult
= NULL
;
1174 result
= _SecItemAdd(attrs
, accessGroups
, &addResult
, &error
);
1176 CFReleaseSafe(addResult
);
1177 CFReleaseSafe(error
);
1178 CFReleaseSafe(attrs
);
1179 CFReleaseSafe(accessGroups
);
1184 /* Specialized version of SecItemAdd for shared web credentials */
1186 _SecAddSharedWebCredential(CFDictionaryRef attributes
,
1187 const audit_token_t
*clientAuditToken
,
1191 CFErrorRef
*error
) {
1193 CFStringRef fqdn
= CFDictionaryGetValue(attributes
, kSecAttrServer
);
1194 CFStringRef account
= CFDictionaryGetValue(attributes
, kSecAttrAccount
);
1195 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1196 CFStringRef password
= CFDictionaryGetValue(attributes
, kSecSharedPassword
);
1198 CFStringRef password
= CFDictionaryGetValue(attributes
, CFSTR("spwd"));
1200 CFStringRef accessGroup
= CFSTR("*");
1201 CFArrayRef accessGroups
= NULL
;
1202 CFMutableDictionaryRef query
= NULL
, attrs
= NULL
;
1204 bool ok
= false, update
= false;
1205 //bool approved = false;
1207 // check autofill enabled status
1208 if (!swca_autofill_enabled(clientAuditToken
)) {
1209 SecError(errSecBadReq
, error
, CFSTR("Autofill is not enabled in Safari settings"));
1213 // parse fqdn with CFURL here, since it could be specified as domain:port
1216 CFStringRef urlStr
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%@%@"), kSecSharedCredentialUrlScheme
, fqdn
);
1218 CFURLRef url
= CFURLCreateWithString(kCFAllocatorDefault
, urlStr
, nil
);
1220 CFStringRef hostname
= CFURLCopyHostName(url
);
1222 CFReleaseSafe(fqdn
);
1224 port
= CFURLGetPortNumber(url
);
1228 CFReleaseSafe(urlStr
);
1233 SecError(errSecParam
, error
, CFSTR("No account provided"));
1237 SecError(errSecParam
, error
, CFSTR("No domain provided"));
1241 #if TARGET_IPHONE_SIMULATOR
1242 secerror("app/site association entitlements not checked in Simulator");
1244 OSStatus status
= errSecMissingEntitlement
;
1245 // validate that fqdn is part of caller's shared credential domains entitlement
1247 SecError(status
, error
, CFSTR("Missing application-identifier entitlement"));
1250 if (_SecEntitlementContainsDomainForService(domains
, fqdn
, kSecSharedWebCredentialsService
)) {
1251 status
= errSecSuccess
;
1253 if (errSecSuccess
!= status
) {
1254 CFStringRef msg
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
,
1255 CFSTR("%@ not found in %@ entitlement"), fqdn
, kSecEntitlementAssociatedDomains
);
1257 msg
= CFRetain(CFSTR("Requested domain not found in entitlement"));
1259 SecError(status
, error
, CFSTR("%@"), msg
);
1265 #if TARGET_IPHONE_SIMULATOR
1266 secerror("Ignoring app/site approval state in the Simulator.");
1268 // get approval status for this app/domain pair
1269 SWCFlags flags
= _SecAppDomainApprovalStatus(appID
, fqdn
, error
);
1270 //approved = ((flags & kSWCFlag_SiteApproved) && (flags & kSWCFlag_UserApproved));
1271 if (!(flags
& kSWCFlag_SiteApproved
)) {
1276 // give ourselves access to see matching items for kSecSafariAccessGroup
1277 accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&accessGroup
, 1, &kCFTypeArrayCallBacks
);
1279 // create lookup query
1280 query
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1282 SecError(errSecAllocate
, error
, CFSTR("Unable to create query dictionary"));
1285 CFDictionaryAddValue(query
, kSecClass
, kSecClassInternetPassword
);
1286 CFDictionaryAddValue(query
, kSecAttrAccessGroup
, kSecSafariAccessGroup
);
1287 CFDictionaryAddValue(query
, kSecAttrAuthenticationType
, kSecAttrAuthenticationTypeHTMLForm
);
1288 CFDictionaryAddValue(query
, kSecAttrServer
, fqdn
);
1289 CFDictionaryAddValue(query
, kSecAttrSynchronizable
, kCFBooleanTrue
);
1291 // check for presence of Safari's negative entry ('passwords not saved')
1292 CFDictionarySetValue(query
, kSecAttrAccount
, kSecSafariPasswordsNotSaved
);
1293 ok
= _SecItemCopyMatching(query
, accessGroups
, result
, error
);
1294 CFReleaseNull(*result
);
1295 CFReleaseNull(*error
);
1297 SecError(errSecDuplicateItem
, error
, CFSTR("Item already exists for this server"));
1301 // now use the provided account (and optional port number, if one was present)
1302 CFDictionarySetValue(query
, kSecAttrAccount
, account
);
1303 if (port
< -1 || port
> 0) {
1304 SInt16 portValueShort
= (port
& 0xFFFF);
1305 CFNumberRef portNumber
= CFNumberCreate(NULL
, kCFNumberSInt16Type
, &portValueShort
);
1306 CFDictionaryAddValue(query
, kSecAttrPort
, portNumber
);
1307 CFReleaseSafe(portNumber
);
1310 // look up existing password
1311 if (_SecItemCopyMatching(query
, accessGroups
, result
, error
)) {
1312 // found it, so this becomes either an "update password" or "delete password" operation
1313 CFReleaseNull(*result
);
1314 CFReleaseNull(*error
);
1315 update
= (password
!= NULL
);
1317 attrs
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1318 CFDataRef credential
= CFStringCreateExternalRepresentation(kCFAllocatorDefault
, password
, kCFStringEncodingUTF8
, 0);
1319 CFDictionaryAddValue(attrs
, kSecValueData
, credential
);
1320 CFReleaseSafe(credential
);
1321 CFDictionaryAddValue(attrs
, kSecAttrComment
, kSecSafariDefaultComment
);
1323 // confirm the update
1324 // (per rdar://16676310 we always prompt, even if there was prior user approval)
1325 ok
= /*approved ||*/ swca_confirm_operation(swca_update_request_id
, clientAuditToken
, query
, error
,
1326 ^void (CFStringRef fqdn
) { _SecAddNegativeWebCredential(fqdn
, appID
, false); });
1328 ok
= _SecItemUpdate(query
, attrs
, accessGroups
, error
);
1332 // confirm the delete
1333 // (per rdar://16676288 we always prompt, even if there was prior user approval)
1334 ok
= /*approved ||*/ swca_confirm_operation(swca_delete_request_id
, clientAuditToken
, query
, error
,
1335 ^void (CFStringRef fqdn
) { _SecAddNegativeWebCredential(fqdn
, appID
, false); });
1337 ok
= _SecItemDelete(query
, accessGroups
, error
);
1341 CFReleaseNull(*error
);
1345 CFReleaseNull(*result
);
1346 CFReleaseNull(*error
);
1348 // password does not exist, so prepare to add it
1350 // a NULL password value removes the existing credential. Since we didn't find it, this is a no-op.
1355 CFStringRef label
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%@ (%@)"), fqdn
, account
);
1357 CFDictionaryAddValue(query
, kSecAttrLabel
, label
);
1358 CFReleaseSafe(label
);
1360 // NOTE: we always expect to use HTTPS for web forms.
1361 CFDictionaryAddValue(query
, kSecAttrProtocol
, kSecAttrProtocolHTTPS
);
1363 CFDataRef credential
= CFStringCreateExternalRepresentation(kCFAllocatorDefault
, password
, kCFStringEncodingUTF8
, 0);
1364 CFDictionarySetValue(query
, kSecValueData
, credential
);
1365 CFReleaseSafe(credential
);
1366 CFDictionarySetValue(query
, kSecAttrComment
, kSecSafariDefaultComment
);
1368 CFReleaseSafe(accessGroups
);
1369 accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&kSecSafariAccessGroup
, 1, &kCFTypeArrayCallBacks
);
1371 // mark the item as created by this function
1372 const int32_t creator_value
= 'swca';
1373 CFNumberRef creator
= CFNumberCreate(kCFAllocatorDefault
, kCFNumberSInt32Type
, &creator_value
);
1375 CFDictionarySetValue(query
, kSecAttrCreator
, creator
);
1376 CFReleaseSafe(creator
);
1381 // (per rdar://16680019, we won't prompt here in the normal case)
1382 ok
= /*approved ||*/ swca_confirm_operation(swca_add_request_id
, clientAuditToken
, query
, error
,
1383 ^void (CFStringRef fqdn
) { _SecAddNegativeWebCredential(fqdn
, appID
, false); });
1387 ok
= _SecItemAdd(query
, accessGroups
, result
, error
);
1391 #if 0 /* debugging */
1393 const char *op_str
= (password
) ? ((update
) ? "updated" : "added") : "deleted";
1394 const char *result_str
= (ok
) ? "true" : "false";
1395 secerror("result=%s, %s item %@, error=%@", result_str
, op_str
, *result
, *error
);
1400 CFReleaseSafe(attrs
);
1401 CFReleaseSafe(query
);
1402 CFReleaseSafe(accessGroups
);
1403 CFReleaseSafe(fqdn
);
1407 /* Specialized version of SecItemCopyMatching for shared web credentials */
1409 _SecCopySharedWebCredential(CFDictionaryRef query
,
1410 const audit_token_t
*clientAuditToken
,
1414 CFErrorRef
*error
) {
1416 CFMutableArrayRef credentials
= NULL
;
1417 CFMutableArrayRef foundItems
= NULL
;
1418 CFMutableArrayRef fqdns
= NULL
;
1419 CFArrayRef accessGroups
= NULL
;
1420 CFStringRef fqdn
= NULL
;
1421 CFStringRef account
= NULL
;
1426 require_quiet(result
, cleanup
);
1427 credentials
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
1428 foundItems
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
1429 fqdns
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
1431 // give ourselves access to see matching items for kSecSafariAccessGroup
1432 CFStringRef accessGroup
= CFSTR("*");
1433 accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&accessGroup
, 1, &kCFTypeArrayCallBacks
);
1435 // On input, the query dictionary contains optional fqdn and account entries.
1436 fqdn
= CFDictionaryGetValue(query
, kSecAttrServer
);
1437 account
= CFDictionaryGetValue(query
, kSecAttrAccount
);
1439 // Check autofill enabled status
1440 if (!swca_autofill_enabled(clientAuditToken
)) {
1441 SecError(errSecBadReq
, error
, CFSTR("Autofill is not enabled in Safari settings"));
1445 // Check fqdn; if NULL, add domains from caller's entitlement.
1447 CFArrayAppendValue(fqdns
, fqdn
);
1450 CFIndex idx
, count
= CFArrayGetCount(domains
);
1451 for (idx
=0; idx
< count
; idx
++) {
1452 CFStringRef str
= (CFStringRef
) CFArrayGetValueAtIndex(domains
, idx
);
1453 // Parse the entry for our service label prefix
1454 if (str
&& CFStringHasPrefix(str
, kSecSharedWebCredentialsService
)) {
1455 CFIndex prefix_len
= CFStringGetLength(kSecSharedWebCredentialsService
)+1;
1456 CFIndex substr_len
= CFStringGetLength(str
) - prefix_len
;
1457 CFRange range
= { prefix_len
, substr_len
};
1458 fqdn
= CFStringCreateWithSubstring(kCFAllocatorDefault
, str
, range
);
1460 CFArrayAppendValue(fqdns
, fqdn
);
1466 count
= CFArrayGetCount(fqdns
);
1468 SecError(errSecParam
, error
, CFSTR("No domain provided"));
1472 // Aggregate search results for each domain
1473 for (idx
= 0; idx
< count
; idx
++) {
1474 CFMutableArrayRef items
= NULL
;
1475 CFMutableDictionaryRef attrs
= NULL
;
1476 fqdn
= (CFStringRef
) CFArrayGetValueAtIndex(fqdns
, idx
);
1480 // Parse the fqdn for a possible port specifier.
1482 CFStringRef urlStr
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%@%@"), kSecSharedCredentialUrlScheme
, fqdn
);
1484 CFURLRef url
= CFURLCreateWithString(kCFAllocatorDefault
, urlStr
, nil
);
1486 CFStringRef hostname
= CFURLCopyHostName(url
);
1488 CFReleaseSafe(fqdn
);
1490 port
= CFURLGetPortNumber(url
);
1494 CFReleaseSafe(urlStr
);
1498 #if TARGET_IPHONE_SIMULATOR
1499 secerror("app/site association entitlements not checked in Simulator");
1501 OSStatus status
= errSecMissingEntitlement
;
1503 SecError(status
, error
, CFSTR("Missing application-identifier entitlement"));
1504 CFReleaseSafe(fqdn
);
1507 // validate that fqdn is part of caller's entitlement
1508 if (_SecEntitlementContainsDomainForService(domains
, fqdn
, kSecSharedWebCredentialsService
)) {
1509 status
= errSecSuccess
;
1511 if (errSecSuccess
!= status
) {
1512 CFStringRef msg
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
,
1513 CFSTR("%@ not found in %@ entitlement"), fqdn
, kSecEntitlementAssociatedDomains
);
1515 msg
= CFRetain(CFSTR("Requested domain not found in entitlement"));
1517 SecError(status
, error
, CFSTR("%@"), msg
);
1519 CFReleaseSafe(fqdn
);
1524 attrs
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1526 SecError(errSecAllocate
, error
, CFSTR("Unable to create query dictionary"));
1527 CFReleaseSafe(fqdn
);
1530 CFDictionaryAddValue(attrs
, kSecClass
, kSecClassInternetPassword
);
1531 CFDictionaryAddValue(attrs
, kSecAttrAccessGroup
, kSecSafariAccessGroup
);
1532 CFDictionaryAddValue(attrs
, kSecAttrAuthenticationType
, kSecAttrAuthenticationTypeHTMLForm
);
1533 CFDictionaryAddValue(attrs
, kSecAttrServer
, fqdn
);
1535 CFDictionaryAddValue(attrs
, kSecAttrAccount
, account
);
1537 if (port
< -1 || port
> 0) {
1538 SInt16 portValueShort
= (port
& 0xFFFF);
1539 CFNumberRef portNumber
= CFNumberCreate(NULL
, kCFNumberSInt16Type
, &portValueShort
);
1540 CFDictionaryAddValue(attrs
, kSecAttrPort
, portNumber
);
1541 CFReleaseSafe(portNumber
);
1543 CFDictionaryAddValue(attrs
, kSecAttrSynchronizable
, kCFBooleanTrue
);
1544 CFDictionaryAddValue(attrs
, kSecMatchLimit
, kSecMatchLimitAll
);
1545 CFDictionaryAddValue(attrs
, kSecReturnAttributes
, kCFBooleanTrue
);
1546 CFDictionaryAddValue(attrs
, kSecReturnData
, kCFBooleanTrue
);
1548 ok
= _SecItemCopyMatching(attrs
, accessGroups
, (CFTypeRef
*)&items
, error
);
1550 // ignore interim error since we have multiple domains to search
1551 CFReleaseNull(*error
);
1553 if (ok
&& items
&& CFGetTypeID(items
) == CFArrayGetTypeID()) {
1554 #if TARGET_IPHONE_SIMULATOR
1555 secerror("Ignoring app/site approval state in the Simulator.");
1556 bool approved
= true;
1558 // get approval status for this app/domain pair
1559 SWCFlags flags
= _SecAppDomainApprovalStatus(appID
, fqdn
, error
);
1561 // ignore interim error since we have multiple domains to check
1562 CFReleaseNull(*error
);
1564 bool approved
= (flags
& kSWCFlag_SiteApproved
);
1567 CFArrayAppendArray(foundItems
, items
, CFRangeMake(0, CFArrayGetCount(items
)));
1570 CFReleaseSafe(items
);
1571 CFReleaseSafe(attrs
);
1572 CFReleaseSafe(fqdn
);
1575 // If matching credentials are found, the credentials provided to the completionHandler
1576 // will be a CFArrayRef containing CFDictionaryRef entries. Each dictionary entry will
1577 // contain the following pairs (see Security/SecItem.h):
1578 // key: kSecAttrServer value: CFStringRef (the website)
1579 // key: kSecAttrAccount value: CFStringRef (the account)
1580 // key: kSecSharedPassword value: CFStringRef (the password)
1582 // key: kSecAttrPort value: CFNumberRef (the port number, if non-standard for https)
1584 count
= CFArrayGetCount(foundItems
);
1585 for (idx
= 0; idx
< count
; idx
++) {
1586 CFDictionaryRef dict
= (CFDictionaryRef
) CFArrayGetValueAtIndex(foundItems
, idx
);
1587 CFMutableDictionaryRef newdict
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1588 if (newdict
&& dict
&& CFGetTypeID(dict
) == CFDictionaryGetTypeID()) {
1589 CFStringRef srvr
= CFDictionaryGetValue(dict
, kSecAttrServer
);
1590 CFStringRef acct
= CFDictionaryGetValue(dict
, kSecAttrAccount
);
1591 CFNumberRef pnum
= CFDictionaryGetValue(dict
, kSecAttrPort
);
1592 CFStringRef icmt
= CFDictionaryGetValue(dict
, kSecAttrComment
);
1593 CFDataRef data
= CFDictionaryGetValue(dict
, kSecValueData
);
1595 CFDictionaryAddValue(newdict
, kSecAttrServer
, srvr
);
1598 CFDictionaryAddValue(newdict
, kSecAttrAccount
, acct
);
1602 if (CFNumberGetValue(pnum
, kCFNumberSInt16Type
, &pval
) &&
1603 (pval
< -1 || pval
> 0)) {
1604 CFDictionaryAddValue(newdict
, kSecAttrPort
, pnum
);
1608 CFStringRef password
= CFStringCreateFromExternalRepresentation(kCFAllocatorDefault
, data
, kCFStringEncodingUTF8
);
1610 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1611 CFDictionaryAddValue(newdict
, kSecSharedPassword
, password
);
1613 CFDictionaryAddValue(newdict
, CFSTR("spwd"), password
);
1615 CFReleaseSafe(password
);
1618 if (icmt
&& CFEqual(icmt
, kSecSafariDefaultComment
)) {
1619 CFArrayInsertValueAtIndex(credentials
, 0, newdict
);
1621 CFArrayAppendValue(credentials
, newdict
);
1624 CFReleaseSafe(newdict
);
1631 // create a new array of dictionaries (without the actual password) for picker UI
1632 count
= CFArrayGetCount(credentials
);
1633 CFMutableArrayRef items
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
1634 for (idx
= 0; idx
< count
; idx
++) {
1635 CFDictionaryRef dict
= (CFDictionaryRef
) CFArrayGetValueAtIndex(credentials
, idx
);
1636 CFMutableDictionaryRef newdict
= CFDictionaryCreateMutableCopy(kCFAllocatorDefault
, 0, dict
);
1637 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1638 CFDictionaryRemoveValue(newdict
, kSecSharedPassword
);
1640 CFDictionaryRemoveValue(newdict
, CFSTR("spwd"));
1642 CFArrayAppendValue(items
, newdict
);
1643 CFReleaseSafe(newdict
);
1646 // prompt user to select one of the dictionary items
1647 CFDictionaryRef selected
= swca_copy_selected_dictionary(swca_select_request_id
,
1648 clientAuditToken
, items
, error
);
1650 // find the matching item in our credentials array
1651 CFStringRef srvr
= CFDictionaryGetValue(selected
, kSecAttrServer
);
1652 CFStringRef acct
= CFDictionaryGetValue(selected
, kSecAttrAccount
);
1653 CFNumberRef pnum
= CFDictionaryGetValue(selected
, kSecAttrPort
);
1654 for (idx
= 0; idx
< count
; idx
++) {
1655 CFDictionaryRef dict
= (CFDictionaryRef
) CFArrayGetValueAtIndex(credentials
, idx
);
1656 CFStringRef srvr1
= CFDictionaryGetValue(dict
, kSecAttrServer
);
1657 CFStringRef acct1
= CFDictionaryGetValue(dict
, kSecAttrAccount
);
1658 CFNumberRef pnum1
= CFDictionaryGetValue(dict
, kSecAttrPort
);
1660 if (!srvr
|| !srvr1
|| !CFEqual(srvr
, srvr1
)) continue;
1661 if (!acct
|| !acct1
|| !CFEqual(acct
, acct1
)) continue;
1662 if ((pnum
&& pnum1
) && !CFEqual(pnum
, pnum1
)) continue;
1665 CFReleaseSafe(selected
);
1672 CFReleaseSafe(items
);
1673 CFArrayRemoveAllValues(credentials
);
1674 if (selected
&& ok
) {
1675 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1676 fqdn
= CFDictionaryGetValue(selected
, kSecAttrServer
);
1678 CFArrayAppendValue(credentials
, selected
);
1682 // confirm the access
1683 ok
= swca_confirm_operation(swca_copy_request_id
, clientAuditToken
, query
, error
,
1684 ^void (CFStringRef fqdn
) { _SecAddNegativeWebCredential(fqdn
, appID
, false); });
1687 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1688 // register confirmation with database
1689 OSStatus status
= _SecSWCEnsuredInitialized();
1691 SecError(status
, error
, CFSTR("SWC initialize failed"));
1693 CFReleaseSafe(selected
);
1696 CFRetainSafe(appID
);
1698 if (0 != sSWCSetServiceFlags_f(kSecSharedWebCredentialsService
,
1699 appID
, fqdn
, kSWCFlag_ExternalMask
, kSWCFlag_UserApproved
,
1700 ^void(OSStatus inStatus
, SWCFlags inNewFlags
){
1701 CFReleaseSafe(appID
);
1702 CFReleaseSafe(fqdn
);
1705 // we didn't queue the block
1706 CFReleaseSafe(appID
);
1707 CFReleaseSafe(fqdn
);
1711 CFReleaseSafe(selected
);
1713 else if (NULL
== *error
) {
1714 // found no items, and we haven't already filled in the error
1715 SecError(errSecItemNotFound
, error
, CFSTR("no matching items found"));
1720 CFArrayRemoveAllValues(credentials
);
1722 CFReleaseSafe(foundItems
);
1723 *result
= credentials
;
1724 CFReleaseSafe(accessGroups
);
1725 CFReleaseSafe(fqdns
);
1726 #if 0 /* debugging */
1727 secerror("result=%s, copied items %@, error=%@", (ok
) ? "true" : "false", *result
, *error
);
1733 // MARK: Keychain backup
1735 CF_RETURNS_RETAINED CFDataRef
1736 _SecServerKeychainBackup(CFDataRef keybag
, CFDataRef passcode
, CFErrorRef
*error
) {
1738 SecDbConnectionRef dbt
= SecDbConnectionAquire(kc_dbhandle(), false, error
);
1743 if (keybag
== NULL
&& passcode
== NULL
) {
1745 backup
= SecServerExportBackupableKeychain(dbt
, KEYBAG_DEVICE
, backup_keybag_handle
, error
);
1746 #else /* !USE_KEYSTORE */
1747 SecError(errSecParam
, error
, CFSTR("Why are you doing this?"));
1749 #endif /* USE_KEYSTORE */
1751 backup
= SecServerKeychainBackup(dbt
, keybag
, passcode
, error
);
1754 SecDbConnectionRelease(dbt
);
1760 _SecServerKeychainRestore(CFDataRef backup
, CFDataRef keybag
, CFDataRef passcode
, CFErrorRef
*error
) {
1761 if (backup
== NULL
|| keybag
== NULL
)
1762 return SecError(errSecParam
, error
, CFSTR("backup or keybag missing"));
1764 __block
bool ok
= true;
1765 ok
&= SecDbPerformWrite(kc_dbhandle(), error
, ^(SecDbConnectionRef dbconn
) {
1766 ok
= SecServerKeychainRestore(dbconn
, backup
, keybag
, passcode
, error
);
1770 SecKeychainChanged(true);
1778 // MARK: SecItemDataSource
1780 // Make sure to call this before any writes to the keychain, so that we fire
1781 // up the engines to monitor manifest changes.
1782 SOSDataSourceFactoryRef
SecItemDataSourceFactoryGetDefault(void) {
1783 return SecItemDataSourceFactoryGetShared(kc_dbhandle());
1786 /* AUDIT[securityd]:
1787 args_in (ok) is a caller provided, CFDictionaryRef.
1790 CF_RETURNS_RETAINED CFArrayRef
1791 _SecServerKeychainSyncUpdateMessage(CFDictionaryRef updates
, CFErrorRef
*error
) {
1792 // This never fails, trust us!
1793 return SOSCCHandleUpdateMessage(updates
);
1797 // Truthiness in the cloud backup/restore support.
1800 static CFDictionaryRef
1801 _SecServerCopyTruthInTheCloud(CFDataRef keybag
, CFDataRef password
,
1802 CFDictionaryRef backup
, CFErrorRef
*error
)
1804 SOSManifestRef mold
= NULL
, mnow
= NULL
, mdelete
= NULL
, madd
= NULL
;
1805 __block CFMutableDictionaryRef backup_new
= NULL
;
1806 keybag_handle_t bag_handle
;
1807 if (!ks_open_keybag(keybag
, password
, &bag_handle
, error
))
1810 // We need to have a datasource singleton for protection domain
1811 // kSecAttrAccessibleWhenUnlocked and keep a single shared engine
1812 // instance around which we create in the datasource constructor as well.
1813 SOSDataSourceFactoryRef dsf
= SecItemDataSourceFactoryGetDefault();
1814 SOSDataSourceRef ds
= SOSDataSourceFactoryCreateDataSource(dsf
, kSecAttrAccessibleWhenUnlocked
, error
);
1816 backup_new
= backup
? CFDictionaryCreateMutableCopy(kCFAllocatorDefault
, 0, backup
) : CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1817 mold
= SOSCreateManifestWithBackup(backup
, error
);
1818 SOSEngineRef engine
= SOSDataSourceGetSharedEngine(ds
, error
);
1819 mnow
= SOSEngineCopyManifest(engine
, NULL
);
1821 mnow
= SOSDataSourceCopyManifestWithViewNameSet(ds
, SOSViewsGetV0ViewSet(), error
);
1824 CFReleaseNull(backup_new
);
1825 secerror("failed to obtain manifest for keychain: %@", error
? *error
: NULL
);
1827 SOSManifestDiff(mold
, mnow
, &mdelete
, &madd
, error
);
1830 // Delete everything from the new_backup that is no longer in the datasource according to the datasources manifest.
1831 SOSManifestForEach(mdelete
, ^(CFDataRef digest_data
, bool *stop
) {
1832 CFStringRef deleted_item_key
= CFDataCopyHexString(digest_data
);
1833 CFDictionaryRemoveValue(backup_new
, deleted_item_key
);
1834 CFRelease(deleted_item_key
);
1837 CFMutableArrayRef changes
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
1838 SOSDataSourceForEachObject(ds
, madd
, error
, ^void(CFDataRef digest
, SOSObjectRef object
, bool *stop
) {
1839 CFErrorRef localError
= NULL
;
1840 CFDataRef digest_data
= NULL
;
1841 CFTypeRef value
= NULL
;
1843 // Key in our manifest can't be found in db, remove it from our manifest
1844 SOSChangesAppendDelete(changes
, digest
);
1845 } else if (!(digest_data
= SOSObjectCopyDigest(ds
, object
, &localError
))
1846 || !(value
= SOSObjectCopyBackup(ds
, object
, bag_handle
, &localError
))) {
1847 if (SecErrorGetOSStatus(localError
) == errSecDecode
) {
1848 // Ignore decode errors, pretend the objects aren't there
1849 CFRelease(localError
);
1850 // Object undecodable, remove it from our manifest
1851 SOSChangesAppendDelete(changes
, digest
);
1853 // Stop iterating and propagate out all other errors.
1855 *error
= localError
;
1856 CFReleaseNull(backup_new
);
1859 // TODO: Should we skip tombstones here?
1860 CFStringRef key
= CFDataCopyHexString(digest_data
);
1861 CFDictionarySetValue(backup_new
, key
, value
);
1864 CFReleaseSafe(digest_data
);
1865 CFReleaseSafe(value
);
1866 }) || CFReleaseNull(backup_new
);
1868 if (CFArrayGetCount(changes
)) {
1869 if (!SOSEngineUpdateChanges(engine
, kSOSDataSourceSOSTransaction
, changes
, error
)) {
1870 CFReleaseNull(backup_new
);
1873 CFReleaseSafe(changes
);
1875 SOSDataSourceRelease(ds
, error
) || CFReleaseNull(backup_new
);
1878 CFReleaseSafe(mold
);
1879 CFReleaseSafe(mnow
);
1880 CFReleaseSafe(madd
);
1881 CFReleaseSafe(mdelete
);
1882 ks_close_keybag(bag_handle
, error
) || CFReleaseNull(backup_new
);
1888 _SecServerRestoreTruthInTheCloud(CFDataRef keybag
, CFDataRef password
, CFDictionaryRef backup_in
, CFErrorRef
*error
) {
1889 __block
bool ok
= true;
1890 keybag_handle_t bag_handle
;
1891 if (!ks_open_keybag(keybag
, password
, &bag_handle
, error
))
1894 SOSManifestRef mbackup
= SOSCreateManifestWithBackup(backup_in
, error
);
1896 SOSDataSourceFactoryRef dsf
= SecItemDataSourceFactoryGetDefault();
1897 SOSDataSourceRef ds
= SOSDataSourceFactoryCreateDataSource(dsf
, kSecAttrAccessibleWhenUnlocked
, error
);
1898 ok
&= ds
&& SOSDataSourceWith(ds
, error
, ^(SOSTransactionRef txn
, bool *commit
) {
1899 SOSManifestRef mnow
= SOSDataSourceCopyManifestWithViewNameSet(ds
, SOSViewsGetV0BackupViewSet(), error
);
1900 SOSManifestRef mdelete
= NULL
, madd
= NULL
;
1901 SOSManifestDiff(mnow
, mbackup
, &mdelete
, &madd
, error
);
1903 // Don't delete everything in datasource not in backup.
1905 // Add items from the backup
1906 SOSManifestForEach(madd
, ^void(CFDataRef e
, bool *stop
) {
1907 CFDictionaryRef item
= NULL
;
1908 CFStringRef sha1
= CFDataCopyHexString(e
);
1910 item
= CFDictionaryGetValue(backup_in
, sha1
);
1914 CFErrorRef localError
= NULL
;
1916 if (!SOSObjectRestoreObject(ds
, txn
, bag_handle
, item
, &localError
)) {
1917 OSStatus status
= SecErrorGetOSStatus(localError
);
1918 if (status
== errSecDuplicateItem
) {
1919 // Log and ignore duplicate item errors during restore
1920 secnotice("titc", "restore %@ not replacing existing item", item
);
1921 } else if (status
== errSecDecode
) {
1922 // Log and ignore corrupted item errors during restore
1923 secnotice("titc", "restore %@ skipping corrupted item %@", item
, localError
);
1925 if (status
== errSecInteractionNotAllowed
)
1927 // Propagate the first other error upwards (causing the restore to fail).
1928 secerror("restore %@ failed %@", item
, localError
);
1930 if (error
&& !*error
) {
1931 *error
= localError
;
1935 CFReleaseSafe(localError
);
1939 ok
&= SOSDataSourceRelease(ds
, error
);
1940 CFReleaseNull(mdelete
);
1941 CFReleaseNull(madd
);
1942 CFReleaseNull(mnow
);
1947 ok
&= ks_close_keybag(bag_handle
, error
);
1953 CF_RETURNS_RETAINED CFDictionaryRef
1954 _SecServerBackupSyncable(CFDictionaryRef backup
, CFDataRef keybag
, CFDataRef password
, CFErrorRef
*error
) {
1955 require_action_quiet(isData(keybag
), errOut
, SecError(errSecParam
, error
, CFSTR("keybag %@ not a data"), keybag
));
1956 require_action_quiet(!backup
|| isDictionary(backup
), errOut
, SecError(errSecParam
, error
, CFSTR("backup %@ not a dictionary"), backup
));
1957 require_action_quiet(!password
|| isData(password
), errOut
, SecError(errSecParam
, error
, CFSTR("password %@ not a data"), password
));
1959 return _SecServerCopyTruthInTheCloud(keybag
, password
, backup
, error
);
1966 _SecServerRestoreSyncable(CFDictionaryRef backup
, CFDataRef keybag
, CFDataRef password
, CFErrorRef
*error
) {
1968 require_action_quiet(isData(keybag
), errOut
, ok
= SecError(errSecParam
, error
, CFSTR("keybag %@ not a data"), keybag
));
1969 require_action_quiet(isDictionary(backup
), errOut
, ok
= SecError(errSecParam
, error
, CFSTR("backup %@ not a dictionary"), backup
));
1971 require_action_quiet(isData(password
), errOut
, ok
= SecError(errSecParam
, error
, CFSTR("password not a data")));
1974 ok
= _SecServerRestoreTruthInTheCloud(keybag
, password
, backup
, error
);
1980 bool _SecServerRollKeys(bool force
, CFErrorRef
*error
) {
1982 uint32_t keystore_generation_status
= 0;
1983 if (aks_generation(KEYBAG_DEVICE
, generation_noop
, &keystore_generation_status
))
1985 uint32_t current_generation
= keystore_generation_status
& generation_current
;
1987 return kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
1988 bool up_to_date
= s3dl_dbt_keys_current(dbt
, current_generation
, NULL
);
1990 if (force
&& !up_to_date
) {
1991 up_to_date
= s3dl_dbt_update_keys(dbt
, error
);
1993 secerror("Completed roll keys.");
1994 up_to_date
= s3dl_dbt_keys_current(dbt
, current_generation
, NULL
);
1997 secerror("Failed to roll keys.");
2007 _SecServerGetKeyStats(const SecDbClass
*qclass
,
2008 struct _SecServerKeyStats
*stats
)
2010 __block CFErrorRef error
= NULL
;
2013 Query
*q
= query_create(qclass
, NULL
, &error
);
2016 q
->q_return_type
= kSecReturnDataMask
| kSecReturnAttributesMask
;
2017 q
->q_limit
= kSecMatchUnlimited
;
2018 q
->q_keybag
= KEYBAG_DEVICE
;
2019 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleWhenUnlocked
, q
);
2020 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleAfterFirstUnlock
, q
);
2021 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleAlways
, q
);
2022 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleWhenUnlockedThisDeviceOnly
, q
);
2023 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
, q
);
2024 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleAlwaysThisDeviceOnly
, q
);
2025 query_add_attribute(kSecAttrTombstone
, kCFBooleanFalse
, q
);
2027 kc_with_dbt(false, &error
, ^(SecDbConnectionRef dbconn
) {
2028 CFErrorRef error2
= NULL
;
2029 __block CFIndex totalSize
= 0;
2030 stats
->maxDataSize
= 0;
2032 SecDbItemSelect(q
, dbconn
, &error2
, NULL
, ^bool(const SecDbAttr
*attr
) {
2033 return CFDictionaryContainsKey(q
->q_item
, attr
->name
);
2034 }, NULL
, NULL
, ^(SecDbItemRef item
, bool *stop
) {
2035 CFErrorRef error3
= NULL
;
2036 CFDataRef data
= SecDbItemGetValue(item
, &v6v_Data
, &error3
);
2038 CFIndex size
= CFDataGetLength(data
);
2039 if (size
> stats
->maxDataSize
)
2040 stats
->maxDataSize
= size
;
2044 CFReleaseNull(error3
);
2046 CFReleaseNull(error2
);
2048 stats
->averageSize
= totalSize
/ stats
->items
;
2057 CFReleaseNull(error
);
2059 query_destroy(q
, NULL
);