2 * Copyright (c) 2006-2014 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 <SecureObjectSync/SOSDigestVector.h>
42 // TODO: Make this include work on both platforms. rdar://problem/16526848
43 #if TARGET_OS_EMBEDDED
44 #include <Security/SecEntitlements.h>
46 /* defines from <Security/SecEntitlements.h> */
47 #define kSecEntitlementAssociatedDomains CFSTR("com.apple.developer.associated-domains")
48 #define kSecEntitlementPrivateAssociatedDomains CFSTR("com.apple.private.associated-domains")
51 #include <utilities/array_size.h>
52 #include <utilities/SecFileLocations.h>
53 #include <Security/SecuritydXPC.h>
54 #include "swcagent_client.h"
57 #include <SharedWebCredentials/SharedWebCredentials.h>
59 typedef uint32_t SWCFlags
;
60 #define kSWCFlags_None 0
61 #define kSWCFlag_Pending ( 1U << 0 )
62 #define kSWCFlag_SiteApproved ( 1U << 1 )
63 #define kSWCFlag_SiteDenied ( 1U << 2 )
64 #define kSWCFlag_UserApproved ( 1U << 3 )
65 #define kSWCFlag_UserDenied ( 1U << 4 )
66 #define kSWCFlag_ExternalMask ( kSWCFlag_UserApproved | kSWCFlag_UserDenied )
69 /* Changed the name of the keychain changed notification, for testing */
70 static const char *g_keychain_changed_notification
= kSecServerKeychainChangedNotification
;
72 void SecItemServerSetKeychainChangedNotification(const char *notification_name
)
74 g_keychain_changed_notification
= notification_name
;
77 void SecKeychainChanged(bool syncWithPeers
) {
78 uint32_t result
= notify_post(g_keychain_changed_notification
);
80 SOSCCSyncWithAllPeers();
81 if (result
== NOTIFY_STATUS_OK
)
82 secnotice("item", "Sent %s%s", syncWithPeers
? "SyncWithAllPeers and " : "", g_keychain_changed_notification
);
84 secerror("%snotify_post %s returned: %" PRIu32
, syncWithPeers
? "Sent SyncWithAllPeers, " : "", g_keychain_changed_notification
, result
);
87 static const char * const s3dl_upgrade_sql
[] = {
93 "CREATE INDEX igsha ON genp(sha1);"
94 "CREATE INDEX iisha ON inet(sha1);"
95 "CREATE INDEX icsha ON cert(sha1);"
96 "CREATE INDEX iksha ON keys(sha1);"
97 "CREATE INDEX ialis ON cert(alis);"
98 "CREATE INDEX isubj ON cert(subj);"
99 "CREATE INDEX iskid ON cert(skid);"
100 "CREATE INDEX ipkhh ON cert(pkhh);"
101 "CREATE INDEX ikcls ON keys(kcls);"
102 "CREATE INDEX iklbl ON keys(klbl);"
103 "CREATE INDEX iencr ON keys(encr);"
104 "CREATE INDEX idecr ON keys(decr);"
105 "CREATE INDEX idrve ON keys(drve);"
106 "CREATE INDEX isign ON keys(sign);"
107 "CREATE INDEX ivrfy ON keys(vrfy);"
108 "CREATE INDEX iwrap ON keys(wrap);"
109 "CREATE INDEX iunwp ON keys(unwp);",
115 /* Rename version 2 or version 3 tables and drop version table since
116 step 0 creates it. */
117 "ALTER TABLE genp RENAME TO ogenp;"
118 "ALTER TABLE inet RENAME TO oinet;"
119 "ALTER TABLE cert RENAME TO ocert;"
120 "ALTER TABLE keys RENAME TO okeys;"
121 "DROP TABLE tversion;",
133 /* Move data from version 5 tables to new ones and drop old ones. */
134 "INSERT INTO genp (rowid,cdat,mdat,desc,icmt,crtr,type,scrp,labl,alis,invi,nega,cusi,prot,acct,svce,gena,data,agrp,pdmn) SELECT rowid,cdat,mdat,desc,icmt,crtr,type,scrp,labl,alis,invi,nega,cusi,prot,acct,svce,gena,data,agrp,pdmn from ogenp;"
135 "INSERT INTO inet (rowid,cdat,mdat,desc,icmt,crtr,type,scrp,labl,alis,invi,nega,cusi,prot,acct,sdmn,srvr,ptcl,atyp,port,path,data,agrp,pdmn) SELECT rowid,cdat,mdat,desc,icmt,crtr,type,scrp,labl,alis,invi,nega,cusi,prot,acct,sdmn,srvr,ptcl,atyp,port,path,data,agrp,pdmn from oinet;"
136 "INSERT INTO cert (rowid,cdat,mdat,ctyp,cenc,labl,alis,subj,issr,slnr,skid,pkhh,data,agrp,pdmn) SELECT rowid,cdat,mdat,ctyp,cenc,labl,alis,subj,issr,slnr,skid,pkhh,data,agrp,pdmn from ocert;"
137 "INSERT INTO keys (rowid,cdat,mdat,kcls,labl,alis,perm,priv,modi,klbl,atag,crtr,type,bsiz,esiz,sdat,edat,sens,asen,extr,next,encr,decr,drve,sign,vrfy,snrc,vyrc,wrap,unwp,data,agrp,pdmn) SELECT rowid,cdat,mdat,kcls,labl,alis,perm,priv,modi,klbl,atag,crtr,type,bsiz,esiz,sdat,edat,sens,asen,extr,next,encr,decr,drve,sign,vrfy,snrc,vyrc,wrap,unwp,data,agrp,pdmn from okeys;"
142 "CREATE INDEX igsha ON genp(sha1);"
143 "CREATE INDEX iisha ON inet(sha1);"
144 "CREATE INDEX icsha ON cert(sha1);"
145 "CREATE INDEX iksha ON keys(sha1);",
152 bool init_pdmn
; // If true do a full export followed by an import of the entire database so all items are re-encoded.
155 /* On disk database format version upgrade scripts.
156 If pre is 0, version is unsupported and db is considered corrupt for having that version.
157 First entry creates the current db, each susequent entry upgrade to current from the version
158 represented by the index of the slot. Each script is either -1 (disabled) of the number of
159 the script in the main table.
160 {pre,main,post, reencode} */
163 static struct sql_stages s3dl_upgrade_script
[] = {
164 { -1, 0, 1, false },/* 0->current: Create version 6*/
165 {}, /* 1->current: Upgrade to version 6 from version 1 -- Unsupported. */
166 {}, /* 2->current: Upgrade to version 6 from version 2 -- Unsupported */
167 {}, /* 3->current: Upgrade to version 6 from version 3 -- Unsupported */
168 {}, /* 4->current: Upgrade to version 6 from version 4 -- Unsupported */
169 { 3, 0, 7, true }, /* 5->current: Upgrade to version 6 from version 5 */
172 static bool sql_run_script(SecDbConnectionRef dbt
, int number
, CFErrorRef
*error
)
174 /* Script -1 == skip this step. */
178 /* If we are attempting to run a script we don't have, fail. */
179 if ((size_t)number
>= array_size(s3dl_upgrade_sql
))
180 return SecDbError(SQLITE_CORRUPT
, error
, CFSTR("script %d exceeds maximum %d"),
181 number
, (int)(array_size(s3dl_upgrade_sql
)));
182 __block
bool ok
= true;
184 CFMutableStringRef sql
= CFStringCreateMutable(0, 0);
185 SecDbAppendCreateTableWithClass(sql
, &genp_class
);
186 SecDbAppendCreateTableWithClass(sql
, &inet_class
);
187 SecDbAppendCreateTableWithClass(sql
, &cert_class
);
188 SecDbAppendCreateTableWithClass(sql
, &keys_class
);
189 CFStringAppend(sql
, CFSTR("CREATE TABLE tversion(version INTEGER);INSERT INTO tversion(version) VALUES(6);"));
190 CFStringPerformWithCString(sql
, ^(const char *sql_string
) {
191 ok
= SecDbErrorWithDb(sqlite3_exec(SecDbHandle(dbt
), sql_string
, NULL
, NULL
, NULL
),
192 SecDbHandle(dbt
), error
, CFSTR("sqlite3_exec: %s"), sql_string
);
196 ok
= SecDbErrorWithDb(sqlite3_exec(SecDbHandle(dbt
), s3dl_upgrade_sql
[number
], NULL
, NULL
, NULL
),
197 SecDbHandle(dbt
), error
, CFSTR("sqlite3_exec: %s"), s3dl_upgrade_sql
[number
]);
202 /* Return the current database version in *version. Returns a
204 static bool s3dl_dbt_get_version(SecDbConnectionRef dbt
, int *version
, CFErrorRef
*error
)
206 CFStringRef sql
= CFSTR("SELECT version FROM tversion LIMIT 1");
207 return SecDbWithSQL(dbt
, sql
, error
, ^(sqlite3_stmt
*stmt
) {
208 __block
bool found_version
= false;
209 bool step_ok
= SecDbForEach(stmt
, error
, ^(int row_index __unused
) {
210 if (!found_version
) {
211 *version
= sqlite3_column_int(stmt
, 0);
212 found_version
= true;
214 return found_version
;
216 if (!found_version
) {
217 /* We have a tversion table but we didn't find a single version
218 value, now what? I suppose we pretend the db is corrupted
219 since this isn't supposed to ever happen. */
220 step_ok
= SecDbError(SQLITE_CORRUPT
, error
, CFSTR("Failed to read version: database corrupt"));
221 secwarning("SELECT version step: %@", error
? *error
: NULL
);
228 static bool s3dl_dbt_upgrade_from_version(SecDbConnectionRef dbt
, int version
, CFErrorRef
*error
)
230 /* We need to go from db version to CURRENT_DB_VERSION, let's do so. */
231 __block
bool ok
= true;
232 /* O, guess we're done already. */
233 if (version
== CURRENT_DB_VERSION
)
236 if (ok
&& version
< 6) {
237 // Pre v6 keychains need to have WAL enabled, since SecDb only
238 // does this at db creation time.
239 // NOTE: This has to be run outside of a transaction.
240 ok
= (SecDbExec(dbt
, CFSTR("PRAGMA auto_vacuum = FULL"), error
) &&
241 SecDbExec(dbt
, CFSTR("PRAGMA journal_mode = WAL"), error
));
244 // Start a transaction to do the upgrade within
245 if (ok
) { ok
= SecDbTransaction(dbt
, kSecDbExclusiveTransactionType
, error
, ^(bool *commit
) {
246 // Be conservative and get the version again once we start a transaction.
247 int cur_version
= version
;
248 s3dl_dbt_get_version(dbt
, &cur_version
, NULL
);
250 /* If we are attempting to upgrade to a version greater than what we have
251 an upgrade script for, fail. */
252 if (ok
&& (cur_version
< 0 ||
253 (size_t)cur_version
>= array_size(s3dl_upgrade_script
))) {
254 ok
= SecDbError(SQLITE_CORRUPT
, error
, CFSTR("no upgrade script for version: %d"), cur_version
);
255 secerror("no upgrade script for version %d", cur_version
);
258 struct sql_stages
*script
;
260 script
= &s3dl_upgrade_script
[cur_version
];
261 if (script
->pre
== 0)
262 ok
= SecDbError(SQLITE_CORRUPT
, error
, CFSTR("unsupported db version %d"), cur_version
);
265 ok
= sql_run_script(dbt
, script
->pre
, error
);
267 ok
= sql_run_script(dbt
, script
->main
, error
);
269 ok
= sql_run_script(dbt
, script
->post
, error
);
270 if (ok
&& script
->init_pdmn
) {
271 CFErrorRef localError
= NULL
;
272 CFDictionaryRef backup
= SecServerExportKeychainPlist(dbt
,
273 KEYBAG_DEVICE
, KEYBAG_NONE
, kSecNoItemFilter
, &localError
);
276 secerror("Ignoring export error: %@ during upgrade export", localError
);
277 CFReleaseNull(localError
);
279 ok
= SecServerImportKeychainInPlist(dbt
, KEYBAG_NONE
,
280 KEYBAG_DEVICE
, backup
, kSecNoItemFilter
, &localError
);
285 if (localError
&& SecErrorGetOSStatus(localError
) == errSecInteractionNotAllowed
) {
286 SecError(errSecUpgradePending
, error
,
287 CFSTR("unable to complete upgrade due to device lock state"));
288 secerror("unable to complete upgrade due to device lock state");
290 secerror("unable to complete upgrade for unknown reason, marking DB as corrupt: %@", localError
);
296 if (error
&& !*error
)
299 CFRelease(localError
);
302 secerror("unable to complete upgrade scripts, marking DB as corrupt: %@", error
? *error
: NULL
);
307 secerror("unable to complete upgrade scripts, marking DB as corrupt: %@", error
? *error
: NULL
);
315 /* This function is called if the db doesn't have the proper version. We
316 start an exclusive transaction and recheck the version, and then perform
317 the upgrade within that transaction. */
318 static bool s3dl_dbt_upgrade(SecDbConnectionRef dbt
, CFErrorRef
*error
)
320 // Already in a transaction
321 //return kc_transaction(dbt, error, ^{
322 int version
= 0; // Upgrade from version 0 == create new db
323 s3dl_dbt_get_version(dbt
, &version
, NULL
);
324 return s3dl_dbt_upgrade_from_version(dbt
, version
, error
);
328 /* AUDIT[securityd](done):
329 accessGroup (ok) is a caller provided, non NULL CFTypeRef.
331 Return true iff accessGroup is allowable according to accessGroups.
333 static bool accessGroupsAllows(CFArrayRef accessGroups
,
334 CFStringRef accessGroup
) {
335 /* NULL accessGroups is wildcard. */
338 /* Make sure we have a string. */
339 if (!isString(accessGroup
))
342 /* Having the special accessGroup "*" allows access to all accessGroups. */
343 CFRange range
= { 0, CFArrayGetCount(accessGroups
) };
345 (CFArrayContainsValue(accessGroups
, range
, accessGroup
) ||
346 CFArrayContainsValue(accessGroups
, range
, CFSTR("*"))))
352 bool itemInAccessGroup(CFDictionaryRef item
, CFArrayRef accessGroups
) {
353 return accessGroupsAllows(accessGroups
,
354 CFDictionaryGetValue(item
, kSecAttrAccessGroup
));
358 static CF_RETURNS_RETAINED CFDataRef
SecServerExportKeychain(SecDbConnectionRef dbt
,
359 keybag_handle_t src_keybag
, keybag_handle_t dest_keybag
, CFErrorRef
*error
) {
360 CFDataRef data_out
= NULL
;
361 /* Export everything except the items for which SecItemIsSystemBound()
363 CFDictionaryRef keychain
= SecServerExportKeychainPlist(dbt
,
364 src_keybag
, dest_keybag
, kSecBackupableItemFilter
,
367 data_out
= CFPropertyListCreateData(kCFAllocatorDefault
, keychain
,
368 kCFPropertyListBinaryFormat_v1_0
,
376 static bool SecServerImportKeychain(SecDbConnectionRef dbt
,
377 keybag_handle_t src_keybag
,
378 keybag_handle_t dest_keybag
, CFDataRef data
, CFErrorRef
*error
) {
379 return kc_transaction(dbt
, error
, ^{
381 CFDictionaryRef keychain
;
382 keychain
= CFPropertyListCreateWithData(kCFAllocatorDefault
, data
,
383 kCFPropertyListImmutable
, NULL
,
386 if (isDictionary(keychain
)) {
387 ok
= SecServerImportKeychainInPlist(dbt
, src_keybag
,
388 dest_keybag
, keychain
,
389 kSecBackupableItemFilter
,
392 ok
= SecError(errSecParam
, error
, CFSTR("import: keychain is not a dictionary"));
400 static CF_RETURNS_RETAINED CFDataRef
SecServerKeychainBackup(SecDbConnectionRef dbt
, CFDataRef keybag
,
401 CFDataRef password
, CFErrorRef
*error
) {
402 CFDataRef backup
= NULL
;
403 keybag_handle_t backup_keybag
;
404 if (ks_open_keybag(keybag
, password
, &backup_keybag
, error
)) {
405 /* Export from system keybag to backup keybag. */
406 backup
= SecServerExportKeychain(dbt
, KEYBAG_DEVICE
, backup_keybag
, error
);
407 if (!ks_close_keybag(backup_keybag
, error
)) {
408 CFReleaseNull(backup
);
414 static bool SecServerKeychainRestore(SecDbConnectionRef dbt
, CFDataRef backup
,
415 CFDataRef keybag
, CFDataRef password
, CFErrorRef
*error
) {
416 keybag_handle_t backup_keybag
;
417 if (!ks_open_keybag(keybag
, password
, &backup_keybag
, error
))
420 /* Import from backup keybag to system keybag. */
421 bool ok
= SecServerImportKeychain(dbt
, backup_keybag
, KEYBAG_DEVICE
,
423 ok
&= ks_close_keybag(backup_keybag
, error
);
429 // MARK - External SPI support code.
431 CFStringRef
__SecKeychainCopyPath(void) {
432 CFStringRef kcRelPath
= NULL
;
434 kcRelPath
= CFSTR("keychain-2.db");
436 kcRelPath
= CFSTR("keychain-2-debug.db");
439 CFStringRef kcPath
= NULL
;
440 CFURLRef kcURL
= SecCopyURLForFileInKeychainDirectory(kcRelPath
);
442 kcPath
= CFURLCopyFileSystemPath(kcURL
, kCFURLPOSIXPathStyle
);
450 // MARK: kc_dbhandle init and reset
452 SecDbRef
SecKeychainDbCreate(CFStringRef path
) {
453 return SecDbCreate(path
, ^bool (SecDbConnectionRef dbconn
, bool didCreate
, CFErrorRef
*localError
) {
456 ok
= s3dl_dbt_upgrade_from_version(dbconn
, 0, localError
);
458 ok
= s3dl_dbt_upgrade(dbconn
, localError
);
461 secerror("Upgrade %sfailed: %@", didCreate
? "from v0 " : "", localError
? *localError
: NULL
);
467 static SecDbRef _kc_dbhandle
= NULL
;
469 static void kc_dbhandle_init(void) {
470 SecDbRef oldHandle
= _kc_dbhandle
;
472 CFStringRef dbPath
= __SecKeychainCopyPath();
474 _kc_dbhandle
= SecKeychainDbCreate(dbPath
);
477 secerror("no keychain path available");
480 secerror("replaced %@ with %@", oldHandle
, _kc_dbhandle
);
481 CFRelease(oldHandle
);
485 static dispatch_once_t _kc_dbhandle_once
;
487 static SecDbRef
kc_dbhandle(void) {
488 dispatch_once(&_kc_dbhandle_once
, ^{
494 /* For whitebox testing only */
495 void kc_dbhandle_reset(void);
496 void kc_dbhandle_reset(void)
498 __block
bool done
= false;
499 dispatch_once(&_kc_dbhandle_once
, ^{
503 // TODO: Not thread safe at all! - FOR DEBUGGING ONLY
508 static SecDbConnectionRef
kc_aquire_dbt(bool writeAndRead
, CFErrorRef
*error
) {
509 SecDbRef db
= kc_dbhandle();
511 SecError(errSecDataNotAvailable
, error
, CFSTR("failed to get a db handle"));
514 return SecDbConnectionAquire(db
, !writeAndRead
, error
);
517 /* Return a per thread dbt handle for the keychain. If create is true create
518 the database if it does not yet exist. If it is false, just return an
519 error if it fails to auto-create. */
520 static bool kc_with_dbt(bool writeAndRead
, CFErrorRef
*error
, bool (^perform
)(SecDbConnectionRef dbt
))
522 // Make sure we initialize our engines before writing to the keychain
524 SecItemDataSourceFactoryGetDefault();
527 SecDbConnectionRef dbt
= kc_aquire_dbt(writeAndRead
, error
);
530 SecDbConnectionRelease(dbt
);
536 items_matching_issuer_parent(SecDbConnectionRef dbt
, CFArrayRef accessGroups
,
537 CFDataRef issuer
, CFArrayRef issuers
, int recurse
)
540 CFArrayRef results
= NULL
;
544 if (CFArrayContainsValue(issuers
, CFRangeMake(0, CFArrayGetCount(issuers
)), issuer
))
547 const void *keys
[] = { kSecClass
, kSecReturnRef
, kSecAttrSubject
};
548 const void *vals
[] = { kSecClassCertificate
, kCFBooleanTrue
, issuer
};
549 CFDictionaryRef query
= CFDictionaryCreate(kCFAllocatorDefault
, keys
, vals
, array_size(keys
), NULL
, NULL
);
554 CFErrorRef localError
= NULL
;
555 q
= query_create_with_limit(query
, kSecMatchUnlimited
, &localError
);
558 s3dl_copy_matching(dbt
, q
, (CFTypeRef
*)&results
, accessGroups
, &localError
);
559 query_destroy(q
, &localError
);
562 secerror("items matching issuer parent: %@", localError
);
563 CFReleaseNull(localError
);
567 count
= CFArrayGetCount(results
);
568 for (i
= 0; (i
< count
) && !found
; i
++) {
569 CFDictionaryRef cert_dict
= (CFDictionaryRef
)CFArrayGetValueAtIndex(results
, i
);
570 CFDataRef cert_issuer
= CFDictionaryGetValue(cert_dict
, kSecAttrIssuer
);
571 if (CFEqual(cert_issuer
, issuer
))
574 found
= items_matching_issuer_parent(dbt
, accessGroups
, cert_issuer
, issuers
, recurse
);
576 CFReleaseSafe(results
);
581 bool match_item(SecDbConnectionRef dbt
, Query
*q
, CFArrayRef accessGroups
, CFDictionaryRef item
)
583 if (q
->q_match_issuer
) {
584 CFDataRef issuer
= CFDictionaryGetValue(item
, kSecAttrIssuer
);
585 if (!items_matching_issuer_parent(dbt
, accessGroups
, issuer
, q
->q_match_issuer
, 10 /*max depth*/))
589 /* Add future match checks here. */
594 /****************************************************************************
595 **************** Beginning of Externally Callable Interface ****************
596 ****************************************************************************/
599 // TODO Use as a safety wrapper
600 static bool SecErrorWith(CFErrorRef
*in_error
, bool (^perform
)(CFErrorRef
*error
)) {
601 CFErrorRef error
= in_error
? *in_error
: NULL
;
603 if ((ok
= perform(&error
))) {
604 assert(error
== NULL
);
606 secerror("error + success: %@", error
);
609 OSStatus status
= SecErrorGetOSStatus(error
);
610 if (status
!= errSecItemNotFound
) // Occurs in normal operation, so exclude
611 secerror("error:[%" PRIdOSStatus
"] %@", status
, error
);
615 CFReleaseNull(error
);
622 /* AUDIT[securityd](done):
623 query (ok) is a caller provided dictionary, only its cf type has been checked.
626 SecItemServerCopyMatching(CFDictionaryRef query
, CFTypeRef
*result
,
627 CFArrayRef accessGroups
, CFErrorRef
*error
)
630 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
))) {
631 return SecError(errSecMissingEntitlement
, error
,
632 CFSTR("client has neither application-identifier nor keychain-access-groups entitlements"));
635 if (CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), CFSTR("*"))) {
636 /* Having the special accessGroup "*" allows access to all accessGroups. */
641 Query
*q
= query_create_with_limit(query
, 1, error
);
643 CFStringRef agrp
= CFDictionaryGetValue(q
->q_item
, kSecAttrAccessGroup
);
644 if (agrp
&& accessGroupsAllows(accessGroups
, agrp
)) {
645 // TODO: Return an error if agrp is not NULL and accessGroupsAllows() fails above.
646 const void *val
= agrp
;
647 accessGroups
= CFArrayCreate(0, &val
, 1, &kCFTypeArrayCallBacks
);
649 CFRetainSafe(accessGroups
);
652 query_enable_interactive(q
);
653 query_set_caller_access_groups(q
, accessGroups
);
655 /* Sanity check the query. */
656 if (q
->q_use_item_list
) {
657 ok
= SecError(errSecUseItemListUnsupported
, error
, CFSTR("use item list unsupported"));
658 #if defined(MULTIPLE_KEYCHAINS)
659 } else if (q
->q_use_keychain
) {
660 ok
= SecError(errSecUseKeychainUnsupported
, error
, CFSTR("use keychain list unsupported"));
662 } else if (q
->q_match_issuer
&& ((q
->q_class
!= &cert_class
) &&
663 (q
->q_class
!= &identity_class
))) {
664 ok
= SecError(errSecUnsupportedOperation
, error
, CFSTR("unsupported match attribute"));
665 } else if (q
->q_return_type
!= 0 && result
== NULL
) {
666 ok
= SecError(errSecReturnMissingPointer
, error
, CFSTR("missing pointer"));
667 } else if (!q
->q_error
) {
669 ok
= kc_with_dbt(false, error
, ^(SecDbConnectionRef dbt
) {
670 return s3dl_copy_matching(dbt
, q
, result
, accessGroups
, error
);
672 } while (query_needs_authentication(q
) && (ok
= query_authenticate(q
, &error
)));
675 CFReleaseSafe(accessGroups
);
676 if (!query_destroy(q
, error
))
684 _SecItemCopyMatching(CFDictionaryRef query
, CFArrayRef accessGroups
, CFTypeRef
*result
, CFErrorRef
*error
) {
685 return SecItemServerCopyMatching(query
, result
, accessGroups
, error
);
688 /* AUDIT[securityd](done):
689 attributes (ok) is a caller provided dictionary, only its cf type has
693 _SecItemAdd(CFDictionaryRef attributes
, CFArrayRef accessGroups
,
694 CFTypeRef
*result
, CFErrorRef
*error
)
698 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
)))
699 return SecError(errSecMissingEntitlement
, error
,
700 CFSTR("client has neither application-identifier nor keychain-access-groups entitlements"));
702 Query
*q
= query_create_with_limit(attributes
, 0, error
);
704 /* Access group sanity checking. */
705 CFStringRef agrp
= (CFStringRef
)CFDictionaryGetValue(attributes
,
706 kSecAttrAccessGroup
);
708 CFArrayRef ag
= accessGroups
;
709 /* Having the special accessGroup "*" allows access to all accessGroups. */
710 if (CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), CFSTR("*")))
714 /* The user specified an explicit access group, validate it. */
715 if (!accessGroupsAllows(accessGroups
, agrp
))
716 return SecError(errSecNoAccessForItem
, error
, CFSTR("NoAccessForItem"));
718 agrp
= (CFStringRef
)CFArrayGetValueAtIndex(ag
, 0);
720 /* We are using an implicit access group, add it as if the user
721 specified it as an attribute. */
722 query_add_attribute(kSecAttrAccessGroup
, agrp
, q
);
725 query_ensure_access_control(q
, agrp
);
726 query_enable_interactive(q
);
729 ok
= SecError(errSecValuePersistentRefUnsupported
, error
, CFSTR("q_row_id")); // TODO: better error string
730 #if defined(MULTIPLE_KEYCHAINS)
731 else if (q
->q_use_keychain_list
)
732 ok
= SecError(errSecUseKeychainListUnsupported
, error
, CFSTR("q_use_keychain_list")); // TODO: better error string;
734 else if (!q
->q_error
) {
736 ok
= kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
){
737 return kc_transaction(dbt
, error
, ^{
738 query_pre_add(q
, true);
739 return s3dl_query_add(dbt
, q
, result
, error
);
742 } while (query_needs_authentication(q
) && (ok
= query_authenticate(q
, &error
)));
744 ok
= query_notify_and_destroy(q
, ok
, error
);
751 /* AUDIT[securityd](done):
752 query (ok) and attributesToUpdate (ok) are a caller provided dictionaries,
753 only their cf types have been checked.
756 _SecItemUpdate(CFDictionaryRef query
, CFDictionaryRef attributesToUpdate
,
757 CFArrayRef accessGroups
, CFErrorRef
*error
)
760 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
))) {
761 return SecError(errSecMissingEntitlement
, error
,
762 CFSTR("client has neither application-identifier nor keychain-access-groups entitlements"));
765 if (CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), CFSTR("*"))) {
766 /* Having the special accessGroup "*" allows access to all accessGroups. */
771 Query
*q
= query_create_with_limit(query
, kSecMatchUnlimited
, error
);
776 /* Sanity check the query. */
777 query_set_caller_access_groups(q
, accessGroups
);
778 if (q
->q_use_item_list
) {
779 ok
= SecError(errSecUseItemListUnsupported
, error
, CFSTR("use item list not supported"));
780 } else if (q
->q_return_type
& kSecReturnDataMask
) {
781 /* Update doesn't return anything so don't ask for it. */
782 ok
= SecError(errSecReturnDataUnsupported
, error
, CFSTR("return data not supported by update"));
783 } else if (q
->q_return_type
& kSecReturnAttributesMask
) {
784 ok
= SecError(errSecReturnAttributesUnsupported
, error
, CFSTR("return attributes not supported by update"));
785 } else if (q
->q_return_type
& kSecReturnRefMask
) {
786 ok
= SecError(errSecReturnRefUnsupported
, error
, CFSTR("return ref not supported by update"));
787 } else if (q
->q_return_type
& kSecReturnPersistentRefMask
) {
788 ok
= SecError(errSecReturnPersistentRefUnsupported
, error
, CFSTR("return persistent ref not supported by update"));
790 /* Access group sanity checking. */
791 CFStringRef agrp
= (CFStringRef
)CFDictionaryGetValue(attributesToUpdate
,
792 kSecAttrAccessGroup
);
794 /* The user is attempting to modify the access group column,
795 validate it to make sure the new value is allowable. */
796 if (!accessGroupsAllows(accessGroups
, agrp
)) {
797 ok
= SecError(errSecNoAccessForItem
, error
, CFSTR("accessGroup %@ not in %@"), agrp
, accessGroups
);
803 if (!q
->q_use_tomb
&& SOSCCThisDeviceDefinitelyNotActiveInCircle()) {
804 q
->q_use_tomb
= kCFBooleanFalse
;
806 query_enable_interactive(q
);
808 ok
= kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
809 return s3dl_query_update(dbt
, q
, attributesToUpdate
, accessGroups
, error
);
811 } while (query_needs_authentication(q
) && (ok
= query_authenticate(q
, &error
)));
814 ok
= query_notify_and_destroy(q
, ok
, error
);
820 /* AUDIT[securityd](done):
821 query (ok) is a caller provided dictionary, only its cf type has been checked.
824 _SecItemDelete(CFDictionaryRef query
, CFArrayRef accessGroups
, CFErrorRef
*error
)
827 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
))) {
828 return SecError(errSecMissingEntitlement
, error
,
829 CFSTR("client has neither application-identifier nor keychain-access-groups entitlements"));
832 if (CFArrayContainsValue(accessGroups
, CFRangeMake(0, ag_count
), CFSTR("*"))) {
833 /* Having the special accessGroup "*" allows access to all accessGroups. */
837 Query
*q
= query_create_with_limit(query
, kSecMatchUnlimited
, error
);
840 q
->q_crypto_op
= kSecKsDelete
;
841 query_set_caller_access_groups(q
, accessGroups
);
842 /* Sanity check the query. */
843 if (q
->q_limit
!= kSecMatchUnlimited
)
844 ok
= SecError(errSecMatchLimitUnsupported
, error
, CFSTR("match limit not supported by delete"));
845 else if (query_match_count(q
) != 0)
846 ok
= SecError(errSecItemMatchUnsupported
, error
, CFSTR("match not supported by delete"));
848 ok
= SecError(errSecValueRefUnsupported
, error
, CFSTR("value ref not supported by delete"));
849 else if (q
->q_row_id
&& query_attr_count(q
))
850 ok
= SecError(errSecItemIllegalQuery
, error
, CFSTR("rowid and other attributes are mutually exclusive"));
852 if (!q
->q_use_tomb
&& SOSCCThisDeviceDefinitelyNotActiveInCircle()) {
853 q
->q_use_tomb
= kCFBooleanFalse
;
855 query_enable_interactive(q
);
857 ok
= kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
858 return s3dl_query_delete(dbt
, q
, accessGroups
, error
);
860 } while (query_needs_authentication(q
) && (ok
= query_authenticate(q
, &error
)));
862 ok
= query_notify_and_destroy(q
, ok
, error
);
870 /* AUDIT[securityd](done):
871 No caller provided inputs.
874 SecItemServerDeleteAll(CFErrorRef
*error
) {
875 return kc_with_dbt(true, error
, ^bool (SecDbConnectionRef dbt
) {
876 return (kc_transaction(dbt
, error
, ^bool {
877 return (SecDbExec(dbt
, CFSTR("DELETE from genp;"), error
) &&
878 SecDbExec(dbt
, CFSTR("DELETE from inet;"), error
) &&
879 SecDbExec(dbt
, CFSTR("DELETE from cert;"), error
) &&
880 SecDbExec(dbt
, CFSTR("DELETE from keys;"), error
));
881 }) && SecDbExec(dbt
, CFSTR("VACUUM;"), error
));
886 _SecItemDeleteAll(CFErrorRef
*error
) {
887 return SecItemServerDeleteAll(error
);
892 // MARK: Shared web credentials
895 #define SEC_CONST_DECL(k,v) CFTypeRef k = (CFTypeRef)(CFSTR(v));
897 SEC_CONST_DECL (kSecSafariAccessGroup
, "com.apple.cfnetwork");
898 SEC_CONST_DECL (kSecSafariDefaultComment
, "default");
899 SEC_CONST_DECL (kSecSafariPasswordsNotSaved
, "Passwords not saved");
900 SEC_CONST_DECL (kSecSharedCredentialUrlScheme
, "https://");
901 SEC_CONST_DECL (kSecSharedWebCredentialsService
, "webcredentials");
903 #if !TARGET_IPHONE_SIMULATOR
905 _SecAppDomainApprovalStatus(CFStringRef appID
, CFStringRef fqdn
, CFErrorRef
*error
)
907 __block SWCFlags flags
= kSWCFlags_None
;
912 dispatch_semaphore_t semaphore
= dispatch_semaphore_create(0);
913 dispatch_retain(semaphore
);
914 if (0 == SWCCheckService(kSecSharedWebCredentialsService
, appID
, fqdn
,
915 ^void (OSStatus inStatus
, SWCFlags inFlags
, CFDictionaryRef inDetails
) {
916 if (!inStatus
) { flags
= inFlags
; }
917 CFReleaseSafe(appID
);
919 dispatch_semaphore_signal(semaphore
);
920 dispatch_release(semaphore
);
921 //secerror("SWCCheckService: inStatus=%d, flags=%0X", inStatus, flags);
924 // wait for the block to complete, as we need its answer
925 dispatch_semaphore_wait(semaphore
, DISPATCH_TIME_FOREVER
);
927 else // didn't queue the block
929 CFReleaseSafe(appID
);
931 dispatch_release(semaphore
);
933 dispatch_release(semaphore
);
935 flags
|= (kSWCFlag_SiteApproved
);
938 if (!error
) { return flags
; }
941 // check website approval status
942 if (!(flags
& kSWCFlag_SiteApproved
)) {
943 if (flags
& kSWCFlag_Pending
) {
944 SecError(errSecAuthFailed
, error
, CFSTR("Approval is pending for \"%@\", try later"), fqdn
);
946 SecError(errSecAuthFailed
, error
, CFSTR("\"%@\" failed to approve \"%@\""), fqdn
, appID
);
951 // check user approval status
952 if (flags
& kSWCFlag_UserDenied
) {
953 SecError(errSecAuthFailed
, error
, CFSTR("User denied access to \"%@\" by \"%@\""), fqdn
, appID
);
959 #if !TARGET_IPHONE_SIMULATOR
961 _SecEntitlementContainsDomainForService(CFArrayRef domains
, CFStringRef domain
, CFStringRef service
)
964 CFIndex idx
, count
= (domains
) ? CFArrayGetCount(domains
) : (CFIndex
) 0;
965 if (!count
|| !domain
|| !service
) {
968 for (idx
=0; idx
< count
; idx
++) {
969 CFStringRef str
= (CFStringRef
) CFArrayGetValueAtIndex(domains
, idx
);
970 if (str
&& CFStringHasPrefix(str
, kSecSharedWebCredentialsService
)) {
971 CFIndex prefix_len
= CFStringGetLength(kSecSharedWebCredentialsService
)+1;
972 CFIndex substr_len
= CFStringGetLength(str
) - prefix_len
;
973 CFRange range
= { prefix_len
, substr_len
};
974 CFStringRef substr
= CFStringCreateWithSubstring(kCFAllocatorDefault
, str
, range
);
975 if (substr
&& CFEqual(substr
, domain
)) {
978 CFReleaseSafe(substr
);
989 _SecAddNegativeWebCredential(CFStringRef fqdn
, CFStringRef appID
, bool forSafari
)
992 if (!fqdn
) { return result
; }
995 // update our database
998 if (0 == SWCSetServiceFlags(kSecSharedWebCredentialsService
,
999 appID
, fqdn
, kSWCFlag_ExternalMask
, kSWCFlag_UserDenied
,
1000 ^void(OSStatus inStatus
, SWCFlags inNewFlags
){
1001 CFReleaseSafe(appID
);
1002 CFReleaseSafe(fqdn
);
1007 else // didn't queue the block
1009 CFReleaseSafe(appID
);
1010 CFReleaseSafe(fqdn
);
1013 if (!forSafari
) { return result
; }
1015 // below this point: create a negative Safari web credential item
1017 CFMutableDictionaryRef attrs
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1018 if (!attrs
) { return result
; }
1020 CFErrorRef error
= NULL
;
1021 CFStringRef accessGroup
= CFSTR("*");
1022 CFArrayRef accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&accessGroup
, 1, &kCFTypeArrayCallBacks
);
1024 CFDictionaryAddValue(attrs
, kSecClass
, kSecClassInternetPassword
);
1025 CFDictionaryAddValue(attrs
, kSecAttrAccessGroup
, kSecSafariAccessGroup
);
1026 CFDictionaryAddValue(attrs
, kSecAttrAuthenticationType
, kSecAttrAuthenticationTypeHTMLForm
);
1027 CFDictionaryAddValue(attrs
, kSecAttrProtocol
, kSecAttrProtocolHTTPS
);
1028 CFDictionaryAddValue(attrs
, kSecAttrServer
, fqdn
);
1029 CFDictionaryAddValue(attrs
, kSecAttrSynchronizable
, kCFBooleanTrue
);
1031 (void)_SecItemDelete(attrs
, accessGroups
, &error
);
1032 CFReleaseNull(error
);
1034 CFDictionaryAddValue(attrs
, kSecAttrAccount
, kSecSafariPasswordsNotSaved
);
1035 CFDictionaryAddValue(attrs
, kSecAttrComment
, kSecSafariDefaultComment
);
1037 CFStringRef label
= CFStringCreateWithFormat(kCFAllocatorDefault
,
1038 NULL
, CFSTR("%@ (%@)"), fqdn
, kSecSafariPasswordsNotSaved
);
1040 CFDictionaryAddValue(attrs
, kSecAttrLabel
, label
);
1041 CFReleaseSafe(label
);
1045 CFDataRef data
= CFDataCreate(kCFAllocatorDefault
, &space
, 1);
1047 CFDictionarySetValue(attrs
, kSecValueData
, data
);
1048 CFReleaseSafe(data
);
1051 CFTypeRef addResult
= NULL
;
1052 result
= _SecItemAdd(attrs
, accessGroups
, &addResult
, &error
);
1054 CFReleaseSafe(addResult
);
1055 CFReleaseSafe(error
);
1056 CFReleaseSafe(attrs
);
1057 CFReleaseSafe(accessGroups
);
1062 /* Specialized version of SecItemAdd for shared web credentials */
1064 _SecAddSharedWebCredential(CFDictionaryRef attributes
,
1065 const audit_token_t
*clientAuditToken
,
1069 CFErrorRef
*error
) {
1071 CFStringRef fqdn
= CFDictionaryGetValue(attributes
, kSecAttrServer
);
1072 CFStringRef account
= CFDictionaryGetValue(attributes
, kSecAttrAccount
);
1073 #if TARGET_OS_IPHONE
1074 CFStringRef password
= CFDictionaryGetValue(attributes
, kSecSharedPassword
);
1076 CFStringRef password
= CFDictionaryGetValue(attributes
, CFSTR("spwd"));
1078 CFStringRef accessGroup
= CFSTR("*");
1079 CFArrayRef accessGroups
= NULL
;
1080 CFMutableDictionaryRef query
= NULL
, attrs
= NULL
;
1082 bool ok
= false, update
= false;
1083 //bool approved = false;
1085 // check autofill enabled status
1086 if (!swca_autofill_enabled(clientAuditToken
)) {
1087 SecError(errSecBadReq
, error
, CFSTR("Autofill is not enabled in Safari settings"));
1091 // parse fqdn with CFURL here, since it could be specified as domain:port
1094 CFStringRef urlStr
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%@%@"), kSecSharedCredentialUrlScheme
, fqdn
);
1096 CFURLRef url
= CFURLCreateWithString(kCFAllocatorDefault
, urlStr
, nil
);
1098 CFStringRef hostname
= CFURLCopyHostName(url
);
1100 CFReleaseSafe(fqdn
);
1102 port
= CFURLGetPortNumber(url
);
1106 CFReleaseSafe(urlStr
);
1111 SecError(errSecParam
, error
, CFSTR("No account provided"));
1115 SecError(errSecParam
, error
, CFSTR("No domain provided"));
1119 #if TARGET_IPHONE_SIMULATOR
1120 secerror("app/site association entitlements not checked in Simulator");
1122 OSStatus status
= errSecMissingEntitlement
;
1123 // validate that fqdn is part of caller's shared credential domains entitlement
1125 SecError(status
, error
, CFSTR("Missing application-identifier entitlement"));
1128 if (_SecEntitlementContainsDomainForService(domains
, fqdn
, kSecSharedWebCredentialsService
)) {
1129 status
= errSecSuccess
;
1131 if (errSecSuccess
!= status
) {
1132 CFStringRef msg
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
,
1133 CFSTR("%@ not found in %@ entitlement"), fqdn
, kSecEntitlementAssociatedDomains
);
1135 msg
= CFRetain(CFSTR("Requested domain not found in entitlement"));
1137 SecError(status
, error
, CFSTR("%@"), msg
);
1143 #if TARGET_IPHONE_SIMULATOR
1144 secerror("Ignoring app/site approval state in the Simulator.");
1146 // get approval status for this app/domain pair
1147 SWCFlags flags
= _SecAppDomainApprovalStatus(appID
, fqdn
, error
);
1148 //approved = ((flags & kSWCFlag_SiteApproved) && (flags & kSWCFlag_UserApproved));
1149 if (!(flags
& kSWCFlag_SiteApproved
)) {
1154 // give ourselves access to see matching items for kSecSafariAccessGroup
1155 accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&accessGroup
, 1, &kCFTypeArrayCallBacks
);
1157 // create lookup query
1158 query
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1160 SecError(errSecAllocate
, error
, CFSTR("Unable to create query dictionary"));
1163 CFDictionaryAddValue(query
, kSecClass
, kSecClassInternetPassword
);
1164 CFDictionaryAddValue(query
, kSecAttrAccessGroup
, kSecSafariAccessGroup
);
1165 CFDictionaryAddValue(query
, kSecAttrAuthenticationType
, kSecAttrAuthenticationTypeHTMLForm
);
1166 CFDictionaryAddValue(query
, kSecAttrServer
, fqdn
);
1167 CFDictionaryAddValue(query
, kSecAttrSynchronizable
, kCFBooleanTrue
);
1169 // check for presence of Safari's negative entry ('passwords not saved')
1170 CFDictionarySetValue(query
, kSecAttrAccount
, kSecSafariPasswordsNotSaved
);
1171 ok
= _SecItemCopyMatching(query
, accessGroups
, result
, error
);
1172 CFReleaseNull(*result
);
1173 CFReleaseNull(*error
);
1175 SecError(errSecDuplicateItem
, error
, CFSTR("Item already exists for this server"));
1179 // now use the provided account (and optional port number, if one was present)
1180 CFDictionarySetValue(query
, kSecAttrAccount
, account
);
1181 if (port
< -1 || port
> 0) {
1182 SInt16 portValueShort
= (port
& 0xFFFF);
1183 CFNumberRef portNumber
= CFNumberCreate(NULL
, kCFNumberSInt16Type
, &portValueShort
);
1184 CFDictionaryAddValue(query
, kSecAttrPort
, portNumber
);
1185 CFReleaseSafe(portNumber
);
1188 // look up existing password
1189 if (_SecItemCopyMatching(query
, accessGroups
, result
, error
)) {
1190 // found it, so this becomes either an "update password" or "delete password" operation
1191 CFReleaseNull(*result
);
1192 CFReleaseNull(*error
);
1193 update
= (password
!= NULL
);
1195 attrs
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1196 CFDataRef credential
= CFStringCreateExternalRepresentation(kCFAllocatorDefault
, password
, kCFStringEncodingUTF8
, 0);
1197 CFDictionaryAddValue(attrs
, kSecValueData
, credential
);
1198 CFReleaseSafe(credential
);
1199 CFDictionaryAddValue(attrs
, kSecAttrComment
, kSecSafariDefaultComment
);
1201 // confirm the update
1202 // (per rdar://16676310 we always prompt, even if there was prior user approval)
1203 ok
= /*approved ||*/ swca_confirm_operation(swca_update_request_id
, clientAuditToken
, query
, error
,
1204 ^void (CFStringRef fqdn
) { _SecAddNegativeWebCredential(fqdn
, appID
, false); });
1206 ok
= _SecItemUpdate(query
, attrs
, accessGroups
, error
);
1210 // confirm the delete
1211 // (per rdar://16676288 we always prompt, even if there was prior user approval)
1212 ok
= /*approved ||*/ swca_confirm_operation(swca_delete_request_id
, clientAuditToken
, query
, error
,
1213 ^void (CFStringRef fqdn
) { _SecAddNegativeWebCredential(fqdn
, appID
, false); });
1215 ok
= _SecItemDelete(query
, accessGroups
, error
);
1219 CFReleaseNull(*error
);
1223 CFReleaseNull(*result
);
1224 CFReleaseNull(*error
);
1226 // password does not exist, so prepare to add it
1228 CFStringRef label
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%@ (%@)"), fqdn
, account
);
1230 CFDictionaryAddValue(query
, kSecAttrLabel
, label
);
1231 CFReleaseSafe(label
);
1233 // NOTE: we always expect to use HTTPS for web forms.
1234 CFDictionaryAddValue(query
, kSecAttrProtocol
, kSecAttrProtocolHTTPS
);
1236 CFDataRef credential
= CFStringCreateExternalRepresentation(kCFAllocatorDefault
, password
, kCFStringEncodingUTF8
, 0);
1237 CFDictionarySetValue(query
, kSecValueData
, credential
);
1238 CFReleaseSafe(credential
);
1239 CFDictionarySetValue(query
, kSecAttrComment
, kSecSafariDefaultComment
);
1241 CFReleaseSafe(accessGroups
);
1242 accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&kSecSafariAccessGroup
, 1, &kCFTypeArrayCallBacks
);
1244 // mark the item as created by this function
1245 const int32_t creator_value
= 'swca';
1246 CFNumberRef creator
= CFNumberCreate(kCFAllocatorDefault
, kCFNumberSInt32Type
, &creator_value
);
1248 CFDictionarySetValue(query
, kSecAttrCreator
, creator
);
1249 CFReleaseSafe(creator
);
1254 // (per rdar://16680019, we won't prompt here in the normal case)
1255 ok
= /*approved ||*/ swca_confirm_operation(swca_add_request_id
, clientAuditToken
, query
, error
,
1256 ^void (CFStringRef fqdn
) { _SecAddNegativeWebCredential(fqdn
, appID
, false); });
1260 ok
= _SecItemAdd(query
, accessGroups
, result
, error
);
1264 #if 0 /* debugging */
1266 const char *op_str
= (password
) ? ((update
) ? "updated" : "added") : "deleted";
1267 const char *result_str
= (ok
) ? "true" : "false";
1268 secerror("result=%s, %s item %@, error=%@", result_str
, op_str
, *result
, *error
);
1273 CFReleaseSafe(attrs
);
1274 CFReleaseSafe(query
);
1275 CFReleaseSafe(accessGroups
);
1276 CFReleaseSafe(fqdn
);
1280 /* Specialized version of SecItemCopyMatching for shared web credentials */
1282 _SecCopySharedWebCredential(CFDictionaryRef query
,
1283 const audit_token_t
*clientAuditToken
,
1287 CFErrorRef
*error
) {
1289 CFMutableArrayRef credentials
= NULL
;
1290 CFMutableArrayRef foundItems
= NULL
;
1291 CFMutableArrayRef fqdns
= NULL
;
1292 CFArrayRef accessGroups
= NULL
;
1293 CFStringRef fqdn
= NULL
;
1294 CFStringRef account
= NULL
;
1299 require_quiet(result
, cleanup
);
1300 credentials
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
1301 foundItems
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
1302 fqdns
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
1304 // give ourselves access to see matching items for kSecSafariAccessGroup
1305 CFStringRef accessGroup
= CFSTR("*");
1306 accessGroups
= CFArrayCreate(kCFAllocatorDefault
, (const void **)&accessGroup
, 1, &kCFTypeArrayCallBacks
);
1308 // On input, the query dictionary contains optional fqdn and account entries.
1309 fqdn
= CFDictionaryGetValue(query
, kSecAttrServer
);
1310 account
= CFDictionaryGetValue(query
, kSecAttrAccount
);
1312 // Check autofill enabled status
1313 if (!swca_autofill_enabled(clientAuditToken
)) {
1314 SecError(errSecBadReq
, error
, CFSTR("Autofill is not enabled in Safari settings"));
1318 // Check fqdn; if NULL, add domains from caller's entitlement.
1320 CFArrayAppendValue(fqdns
, fqdn
);
1323 CFIndex idx
, count
= CFArrayGetCount(domains
);
1324 for (idx
=0; idx
< count
; idx
++) {
1325 CFStringRef str
= (CFStringRef
) CFArrayGetValueAtIndex(domains
, idx
);
1326 // Parse the entry for our service label prefix
1327 if (str
&& CFStringHasPrefix(str
, kSecSharedWebCredentialsService
)) {
1328 CFIndex prefix_len
= CFStringGetLength(kSecSharedWebCredentialsService
)+1;
1329 CFIndex substr_len
= CFStringGetLength(str
) - prefix_len
;
1330 CFRange range
= { prefix_len
, substr_len
};
1331 fqdn
= CFStringCreateWithSubstring(kCFAllocatorDefault
, str
, range
);
1333 CFArrayAppendValue(fqdns
, fqdn
);
1339 count
= CFArrayGetCount(fqdns
);
1341 SecError(errSecParam
, error
, CFSTR("No domain provided"));
1345 // Aggregate search results for each domain
1346 for (idx
= 0; idx
< count
; idx
++) {
1347 CFMutableArrayRef items
= NULL
;
1348 CFMutableDictionaryRef attrs
= NULL
;
1349 fqdn
= (CFStringRef
) CFArrayGetValueAtIndex(fqdns
, idx
);
1353 // Parse the fqdn for a possible port specifier.
1355 CFStringRef urlStr
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%@%@"), kSecSharedCredentialUrlScheme
, fqdn
);
1357 CFURLRef url
= CFURLCreateWithString(kCFAllocatorDefault
, urlStr
, nil
);
1359 CFStringRef hostname
= CFURLCopyHostName(url
);
1361 CFReleaseSafe(fqdn
);
1363 port
= CFURLGetPortNumber(url
);
1367 CFReleaseSafe(urlStr
);
1371 #if TARGET_IPHONE_SIMULATOR
1372 secerror("app/site association entitlements not checked in Simulator");
1374 OSStatus status
= errSecMissingEntitlement
;
1376 SecError(status
, error
, CFSTR("Missing application-identifier entitlement"));
1377 CFReleaseSafe(fqdn
);
1380 // validate that fqdn is part of caller's entitlement
1381 if (_SecEntitlementContainsDomainForService(domains
, fqdn
, kSecSharedWebCredentialsService
)) {
1382 status
= errSecSuccess
;
1384 if (errSecSuccess
!= status
) {
1385 CFStringRef msg
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
,
1386 CFSTR("%@ not found in %@ entitlement"), fqdn
, kSecEntitlementAssociatedDomains
);
1388 msg
= CFRetain(CFSTR("Requested domain not found in entitlement"));
1390 SecError(status
, error
, CFSTR("%@"), msg
);
1392 CFReleaseSafe(fqdn
);
1397 attrs
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1399 SecError(errSecAllocate
, error
, CFSTR("Unable to create query dictionary"));
1400 CFReleaseSafe(fqdn
);
1403 CFDictionaryAddValue(attrs
, kSecClass
, kSecClassInternetPassword
);
1404 CFDictionaryAddValue(attrs
, kSecAttrAccessGroup
, kSecSafariAccessGroup
);
1405 CFDictionaryAddValue(attrs
, kSecAttrAuthenticationType
, kSecAttrAuthenticationTypeHTMLForm
);
1406 CFDictionaryAddValue(attrs
, kSecAttrServer
, fqdn
);
1408 CFDictionaryAddValue(attrs
, kSecAttrAccount
, account
);
1410 if (port
< -1 || port
> 0) {
1411 SInt16 portValueShort
= (port
& 0xFFFF);
1412 CFNumberRef portNumber
= CFNumberCreate(NULL
, kCFNumberSInt16Type
, &portValueShort
);
1413 CFDictionaryAddValue(attrs
, kSecAttrPort
, portNumber
);
1414 CFReleaseSafe(portNumber
);
1416 CFDictionaryAddValue(attrs
, kSecAttrSynchronizable
, kCFBooleanTrue
);
1417 CFDictionaryAddValue(attrs
, kSecMatchLimit
, kSecMatchLimitAll
);
1418 CFDictionaryAddValue(attrs
, kSecReturnAttributes
, kCFBooleanTrue
);
1419 CFDictionaryAddValue(attrs
, kSecReturnData
, kCFBooleanTrue
);
1421 ok
= _SecItemCopyMatching(attrs
, accessGroups
, (CFTypeRef
*)&items
, error
);
1423 // ignore interim error since we have multiple domains to search
1424 CFReleaseNull(*error
);
1426 if (ok
&& items
&& CFGetTypeID(items
) == CFArrayGetTypeID()) {
1427 #if TARGET_IPHONE_SIMULATOR
1428 secerror("Ignoring app/site approval state in the Simulator.");
1429 bool approved
= true;
1431 // get approval status for this app/domain pair
1432 SWCFlags flags
= _SecAppDomainApprovalStatus(appID
, fqdn
, error
);
1434 // ignore interim error since we have multiple domains to check
1435 CFReleaseNull(*error
);
1437 bool approved
= (flags
& kSWCFlag_SiteApproved
);
1440 CFArrayAppendArray(foundItems
, items
, CFRangeMake(0, CFArrayGetCount(items
)));
1443 CFReleaseSafe(items
);
1444 CFReleaseSafe(attrs
);
1445 CFReleaseSafe(fqdn
);
1448 // If matching credentials are found, the credentials provided to the completionHandler
1449 // will be a CFArrayRef containing CFDictionaryRef entries. Each dictionary entry will
1450 // contain the following pairs (see Security/SecItem.h):
1451 // key: kSecAttrServer value: CFStringRef (the website)
1452 // key: kSecAttrAccount value: CFStringRef (the account)
1453 // key: kSecSharedPassword value: CFStringRef (the password)
1455 // key: kSecAttrPort value: CFNumberRef (the port number, if non-standard for https)
1457 count
= CFArrayGetCount(foundItems
);
1458 for (idx
= 0; idx
< count
; idx
++) {
1459 CFDictionaryRef dict
= (CFDictionaryRef
) CFArrayGetValueAtIndex(foundItems
, idx
);
1460 CFMutableDictionaryRef newdict
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1461 if (newdict
&& dict
&& CFGetTypeID(dict
) == CFDictionaryGetTypeID()) {
1462 CFStringRef srvr
= CFDictionaryGetValue(dict
, kSecAttrServer
);
1463 CFStringRef acct
= CFDictionaryGetValue(dict
, kSecAttrAccount
);
1464 CFNumberRef pnum
= CFDictionaryGetValue(dict
, kSecAttrPort
);
1465 CFStringRef icmt
= CFDictionaryGetValue(dict
, kSecAttrComment
);
1466 CFDataRef data
= CFDictionaryGetValue(dict
, kSecValueData
);
1468 CFDictionaryAddValue(newdict
, kSecAttrServer
, srvr
);
1471 CFDictionaryAddValue(newdict
, kSecAttrAccount
, acct
);
1475 if (CFNumberGetValue(pnum
, kCFNumberSInt16Type
, &pval
) &&
1476 (pval
< -1 || pval
> 0)) {
1477 CFDictionaryAddValue(newdict
, kSecAttrPort
, pnum
);
1481 CFStringRef password
= CFStringCreateFromExternalRepresentation(kCFAllocatorDefault
, data
, kCFStringEncodingUTF8
);
1483 #if TARGET_OS_IPHONE
1484 CFDictionaryAddValue(newdict
, kSecSharedPassword
, password
);
1486 CFDictionaryAddValue(newdict
, CFSTR("spwd"), password
);
1488 CFReleaseSafe(password
);
1491 if (icmt
&& CFEqual(icmt
, kSecSafariDefaultComment
)) {
1492 CFArrayInsertValueAtIndex(credentials
, 0, newdict
);
1494 CFArrayAppendValue(credentials
, newdict
);
1497 CFReleaseSafe(newdict
);
1504 // create a new array of dictionaries (without the actual password) for picker UI
1505 count
= CFArrayGetCount(credentials
);
1506 CFMutableArrayRef items
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
1507 for (idx
= 0; idx
< count
; idx
++) {
1508 CFDictionaryRef dict
= (CFDictionaryRef
) CFArrayGetValueAtIndex(credentials
, idx
);
1509 CFMutableDictionaryRef newdict
= CFDictionaryCreateMutableCopy(kCFAllocatorDefault
, 0, dict
);
1510 #if TARGET_OS_IPHONE
1511 CFDictionaryRemoveValue(newdict
, kSecSharedPassword
);
1513 CFDictionaryRemoveValue(newdict
, CFSTR("spwd"));
1515 CFArrayAppendValue(items
, newdict
);
1516 CFReleaseSafe(newdict
);
1519 // prompt user to select one of the dictionary items
1520 CFDictionaryRef selected
= swca_copy_selected_dictionary(swca_select_request_id
,
1521 clientAuditToken
, items
, error
);
1523 // find the matching item in our credentials array
1524 CFStringRef srvr
= CFDictionaryGetValue(selected
, kSecAttrServer
);
1525 CFStringRef acct
= CFDictionaryGetValue(selected
, kSecAttrAccount
);
1526 CFNumberRef pnum
= CFDictionaryGetValue(selected
, kSecAttrPort
);
1527 for (idx
= 0; idx
< count
; idx
++) {
1528 CFDictionaryRef dict
= (CFDictionaryRef
) CFArrayGetValueAtIndex(credentials
, idx
);
1529 CFStringRef srvr1
= CFDictionaryGetValue(dict
, kSecAttrServer
);
1530 CFStringRef acct1
= CFDictionaryGetValue(dict
, kSecAttrAccount
);
1531 CFNumberRef pnum1
= CFDictionaryGetValue(dict
, kSecAttrPort
);
1533 if (!srvr
|| !srvr1
|| !CFEqual(srvr
, srvr1
)) continue;
1534 if (!acct
|| !acct1
|| !CFEqual(acct
, acct1
)) continue;
1535 if ((pnum
&& pnum1
) && !CFEqual(pnum
, pnum1
)) continue;
1538 CFReleaseSafe(selected
);
1545 CFReleaseSafe(items
);
1546 CFArrayRemoveAllValues(credentials
);
1547 if (selected
&& ok
) {
1548 #if TARGET_OS_IPHONE
1549 fqdn
= CFDictionaryGetValue(selected
, kSecAttrServer
);
1551 CFArrayAppendValue(credentials
, selected
);
1555 // confirm the access
1556 ok
= swca_confirm_operation(swca_copy_request_id
, clientAuditToken
, query
, error
,
1557 ^void (CFStringRef fqdn
) { _SecAddNegativeWebCredential(fqdn
, appID
, false); });
1560 #if TARGET_OS_IPHONE
1561 // register confirmation with database
1562 CFRetainSafe(appID
);
1564 if (0 != SWCSetServiceFlags(kSecSharedWebCredentialsService
,
1565 appID
, fqdn
, kSWCFlag_ExternalMask
, kSWCFlag_UserApproved
,
1566 ^void(OSStatus inStatus
, SWCFlags inNewFlags
){
1567 CFReleaseSafe(appID
);
1568 CFReleaseSafe(fqdn
);
1571 // we didn't queue the block
1572 CFReleaseSafe(appID
);
1573 CFReleaseSafe(fqdn
);
1577 CFReleaseSafe(selected
);
1579 else if (NULL
== *error
) {
1580 // found no items, and we haven't already filled in the error
1581 SecError(errSecItemNotFound
, error
, CFSTR("no matching items found"));
1586 CFArrayRemoveAllValues(credentials
);
1588 CFReleaseSafe(foundItems
);
1589 *result
= credentials
;
1590 CFReleaseSafe(accessGroups
);
1591 CFReleaseSafe(fqdns
);
1592 #if 0 /* debugging */
1593 secerror("result=%s, copied items %@, error=%@", (ok
) ? "true" : "false", *result
, *error
);
1599 // MARK: Keychain backup
1601 CF_RETURNS_RETAINED CFDataRef
1602 _SecServerKeychainBackup(CFDataRef keybag
, CFDataRef passcode
, CFErrorRef
*error
) {
1604 SecDbConnectionRef dbt
= SecDbConnectionAquire(kc_dbhandle(), false, error
);
1609 if (keybag
== NULL
&& passcode
== NULL
) {
1611 backup
= SecServerExportKeychain(dbt
, KEYBAG_DEVICE
, backup_keybag_handle
, error
);
1612 #else /* !USE_KEYSTORE */
1613 SecError(errSecParam
, error
, CFSTR("Why are you doing this?"));
1615 #endif /* USE_KEYSTORE */
1617 backup
= SecServerKeychainBackup(dbt
, keybag
, passcode
, error
);
1620 SecDbConnectionRelease(dbt
);
1626 _SecServerKeychainRestore(CFDataRef backup
, CFDataRef keybag
, CFDataRef passcode
, CFErrorRef
*error
) {
1627 if (backup
== NULL
|| keybag
== NULL
)
1628 return SecError(errSecParam
, error
, CFSTR("backup or keybag missing"));
1630 __block
bool ok
= true;
1631 ok
&= SecDbPerformWrite(kc_dbhandle(), error
, ^(SecDbConnectionRef dbconn
) {
1632 ok
= SecServerKeychainRestore(dbconn
, backup
, keybag
, passcode
, error
);
1636 SecKeychainChanged(true);
1643 // MARK: SecItemDataSource
1645 // Make sure to call this before any writes to the keychain, so that we fire
1646 // up the engines to monitor manifest changes.
1647 SOSDataSourceFactoryRef
SecItemDataSourceFactoryGetDefault(void) {
1648 return SecItemDataSourceFactoryGetShared(kc_dbhandle());
1651 /* AUDIT[securityd]:
1652 args_in (ok) is a caller provided, CFDictionaryRef.
1655 CF_RETURNS_RETAINED CFArrayRef
1656 _SecServerKeychainSyncUpdateKeyParameter(CFDictionaryRef updates
, CFErrorRef
*error
) {
1657 // This never fails, trust us!
1658 return SOSCCHandleUpdateKeyParameter(updates
);
1662 CF_RETURNS_RETAINED CFArrayRef
1663 _SecServerKeychainSyncUpdateCircle(CFDictionaryRef updates
, CFErrorRef
*error
) {
1664 // This never fails, trust us!
1665 return SOSCCHandleUpdateCircle(updates
);
1668 CF_RETURNS_RETAINED CFArrayRef
1669 _SecServerKeychainSyncUpdateMessage(CFDictionaryRef updates
, CFErrorRef
*error
) {
1670 // This never fails, trust us!
1671 return SOSCCHandleUpdateMessage(updates
);
1676 // Truthiness in the cloud backup/restore support.
1679 static CFDictionaryRef
1680 _SecServerCopyTruthInTheCloud(CFDataRef keybag
, CFDataRef password
,
1681 CFDictionaryRef backup
, CFErrorRef
*error
)
1683 SOSManifestRef mold
= NULL
, mnow
= NULL
, mdelete
= NULL
, madd
= NULL
;
1684 __block CFMutableDictionaryRef backup_new
= NULL
;
1685 keybag_handle_t bag_handle
;
1686 if (!ks_open_keybag(keybag
, password
, &bag_handle
, error
))
1689 // We need to have a datasource singleton for protection domain
1690 // kSecAttrAccessibleWhenUnlocked and keep a single shared engine
1691 // instance around which we create in the datasource constructor as well.
1692 SOSDataSourceFactoryRef dsf
= SecItemDataSourceFactoryGetDefault();
1693 SOSDataSourceRef ds
= dsf
->create_datasource(dsf
, kSecAttrAccessibleWhenUnlocked
, error
);
1695 backup_new
= backup
? CFDictionaryCreateMutableCopy(kCFAllocatorDefault
, 0, backup
) : CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1696 mold
= SOSCreateManifestWithBackup(backup
, error
);
1697 SOSEngineRef engine
= SOSDataSourceGetSharedEngine(ds
, error
);
1698 mnow
= SOSEngineCopyManifest(engine
, error
);
1700 mnow
= SOSDataSourceCopyManifest(ds
, error
);
1703 CFReleaseNull(backup_new
);
1704 secerror("failed to obtain manifest for keychain: %@", error
? *error
: NULL
);
1706 SOSManifestDiff(mold
, mnow
, &mdelete
, &madd
, error
);
1709 // Delete everything from the new_backup that is no longer in the datasource according to the datasources manifest.
1710 SOSManifestForEach(mdelete
, ^(CFDataRef digest_data
, bool *stop
) {
1711 CFStringRef deleted_item_key
= CFDataCopyHexString(digest_data
);
1712 CFDictionaryRemoveValue(backup_new
, deleted_item_key
);
1713 CFRelease(deleted_item_key
);
1716 __block
struct SOSDigestVector dvdel
= SOSDigestVectorInit
;
1717 SOSDataSourceForEachObject(ds
, madd
, error
, ^void(CFDataRef digest
, SOSObjectRef object
, bool *stop
) {
1718 CFErrorRef localError
= NULL
;
1719 CFDataRef digest_data
= NULL
;
1720 CFTypeRef value
= NULL
;
1722 // Key in our manifest can't be found in db, remove it from our manifest
1723 SOSDigestVectorAppend(&dvdel
, CFDataGetBytePtr(digest
));
1724 } else if (!(digest_data
= SOSObjectCopyDigest(ds
, object
, &localError
))
1725 || !(value
= SOSObjectCopyBackup(ds
, object
, bag_handle
, &localError
))) {
1726 if (SecErrorGetOSStatus(localError
) == errSecDecode
) {
1727 // Ignore decode errors, pretend the objects aren't there
1728 CFRelease(localError
);
1729 // Object undecodable, remove it from our manifest
1730 SOSDigestVectorAppend(&dvdel
, CFDataGetBytePtr(digest
));
1732 // Stop iterating and propagate out all other errors.
1734 *error
= localError
;
1735 CFReleaseNull(backup_new
);
1738 // TODO: Should we skip tombstones here?
1739 CFStringRef key
= CFDataCopyHexString(digest_data
);
1740 CFDictionarySetValue(backup_new
, key
, value
);
1743 CFReleaseSafe(digest_data
);
1744 CFReleaseSafe(value
);
1745 }) || CFReleaseNull(backup_new
);
1748 struct SOSDigestVector dvadd
= SOSDigestVectorInit
;
1749 if (!SOSEngineUpdateLocalManifest(engine
, kSOSDataSourceSOSTransaction
, &dvdel
, &dvadd
, error
)) {
1750 CFReleaseNull(backup_new
);
1752 SOSDigestVectorFree(&dvdel
);
1755 SOSDataSourceRelease(ds
, error
) || CFReleaseNull(backup_new
);
1758 CFReleaseSafe(mold
);
1759 CFReleaseSafe(mnow
);
1760 CFReleaseSafe(madd
);
1761 CFReleaseSafe(mdelete
);
1762 ks_close_keybag(bag_handle
, error
) || CFReleaseNull(backup_new
);
1768 _SecServerRestoreTruthInTheCloud(CFDataRef keybag
, CFDataRef password
, CFDictionaryRef backup_in
, CFErrorRef
*error
) {
1769 __block
bool ok
= true;
1770 keybag_handle_t bag_handle
;
1771 if (!ks_open_keybag(keybag
, password
, &bag_handle
, error
))
1774 SOSManifestRef mbackup
= SOSCreateManifestWithBackup(backup_in
, error
);
1776 SOSDataSourceFactoryRef dsf
= SecItemDataSourceFactoryGetDefault();
1777 SOSDataSourceRef ds
= dsf
->create_datasource(dsf
, kSecAttrAccessibleWhenUnlocked
, error
);
1779 ok
= SOSDataSourceWith(ds
, error
, ^(SOSTransactionRef txn
, bool *commit
) {
1780 SOSManifestRef mnow
= SOSDataSourceCopyManifest(ds
, error
);
1781 SOSManifestRef mdelete
= NULL
, madd
= NULL
;
1782 SOSManifestDiff(mnow
, mbackup
, &mdelete
, &madd
, error
);
1784 // Don't delete everything in datasource not in backup.
1786 // Add items from the backup
1787 SOSManifestForEach(madd
, ^void(CFDataRef e
, bool *stop
) {
1788 CFDictionaryRef item
= NULL
;
1789 CFStringRef sha1
= CFDataCopyHexString(e
);
1791 item
= CFDictionaryGetValue(backup_in
, sha1
);
1795 CFErrorRef localError
= NULL
;
1797 if (!SOSObjectRestoreObject(ds
, txn
, bag_handle
, item
, &localError
)) {
1798 OSStatus status
= SecErrorGetOSStatus(localError
);
1799 if (status
== errSecDuplicateItem
) {
1800 // Log and ignore duplicate item errors during restore
1801 secnotice("titc", "restore %@ not replacing existing item", item
);
1803 if (status
== errSecInteractionNotAllowed
)
1805 // Propagate the first other error upwards (causing the restore to fail).
1806 secerror("restore %@ failed %@", item
, localError
);
1808 if (error
&& !*error
) {
1809 *error
= localError
;
1813 CFReleaseSafe(localError
);
1817 ok
&= SOSDataSourceRelease(ds
, error
);
1818 CFReleaseNull(mdelete
);
1819 CFReleaseNull(madd
);
1820 CFReleaseNull(mnow
);
1828 ok
&= ks_close_keybag(bag_handle
, error
);
1834 CF_RETURNS_RETAINED CFDictionaryRef
1835 _SecServerBackupSyncable(CFDictionaryRef backup
, CFDataRef keybag
, CFDataRef password
, CFErrorRef
*error
) {
1836 require_action_quiet(isData(keybag
), errOut
, SecError(errSecParam
, error
, CFSTR("keybag %@ not a data"), keybag
));
1837 require_action_quiet(!backup
|| isDictionary(backup
), errOut
, SecError(errSecParam
, error
, CFSTR("backup %@ not a dictionary"), backup
));
1838 require_action_quiet(!password
|| isData(password
), errOut
, SecError(errSecParam
, error
, CFSTR("password %@ not a data"), password
));
1840 return _SecServerCopyTruthInTheCloud(keybag
, password
, backup
, error
);
1847 _SecServerRestoreSyncable(CFDictionaryRef backup
, CFDataRef keybag
, CFDataRef password
, CFErrorRef
*error
) {
1849 require_action_quiet(isData(keybag
), errOut
, ok
= SecError(errSecParam
, error
, CFSTR("keybag %@ not a data"), keybag
));
1850 require_action_quiet(isDictionary(backup
), errOut
, ok
= SecError(errSecParam
, error
, CFSTR("backup %@ not a dictionary"), backup
));
1852 require_action_quiet(isData(password
), errOut
, ok
= SecError(errSecParam
, error
, CFSTR("password not a data")));
1855 ok
= _SecServerRestoreTruthInTheCloud(keybag
, password
, backup
, error
);
1861 bool _SecServerRollKeys(bool force
, CFErrorRef
*error
) {
1863 uint32_t keystore_generation_status
= 0;
1864 if (aks_generation(KEYBAG_DEVICE
, generation_noop
, &keystore_generation_status
))
1866 uint32_t current_generation
= keystore_generation_status
& generation_current
;
1868 return kc_with_dbt(true, error
, ^(SecDbConnectionRef dbt
) {
1869 bool up_to_date
= s3dl_dbt_keys_current(dbt
, current_generation
, NULL
);
1871 if (force
&& !up_to_date
) {
1872 up_to_date
= s3dl_dbt_update_keys(dbt
, error
);
1874 secerror("Completed roll keys.");
1875 up_to_date
= s3dl_dbt_keys_current(dbt
, current_generation
, NULL
);
1878 secerror("Failed to roll keys.");