]> git.saurik.com Git - apple/security.git/blob - Security/sec/securityd/SecItemServer.c
Security-57031.30.12.tar.gz
[apple/security.git] / Security / sec / securityd / SecItemServer.c
1 /*
2 * Copyright (c) 2006-2014 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
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
11 * file.
12 *
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.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24 /*
25 * SecItemServer.c - CoreFoundation-based constants and functions for
26 access to Security items (certificates, keys, identities, and
27 passwords.)
28 */
29
30 #include <securityd/SecItemServer.h>
31
32 #include <notify.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>
41
42 // TODO: Make this include work on both platforms. rdar://problem/16526848
43 #if TARGET_OS_EMBEDDED
44 #include <Security/SecEntitlements.h>
45 #else
46 /* defines from <Security/SecEntitlements.h> */
47 #define kSecEntitlementAssociatedDomains CFSTR("com.apple.developer.associated-domains")
48 #define kSecEntitlementPrivateAssociatedDomains CFSTR("com.apple.private.associated-domains")
49 #endif
50
51 #include <utilities/array_size.h>
52 #include <utilities/SecFileLocations.h>
53 #include <Security/SecuritydXPC.h>
54 #include "swcagent_client.h"
55
56 #if TARGET_OS_IPHONE
57 #include <SharedWebCredentials/SharedWebCredentials.h>
58 #else
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 )
67 #endif
68
69 /* Changed the name of the keychain changed notification, for testing */
70 static const char *g_keychain_changed_notification = kSecServerKeychainChangedNotification;
71
72 void SecItemServerSetKeychainChangedNotification(const char *notification_name)
73 {
74 g_keychain_changed_notification = notification_name;
75 }
76
77 void SecKeychainChanged(bool syncWithPeers) {
78 uint32_t result = notify_post(g_keychain_changed_notification);
79 if (syncWithPeers)
80 SOSCCSyncWithAllPeers();
81 if (result == NOTIFY_STATUS_OK)
82 secnotice("item", "Sent %s%s", syncWithPeers ? "SyncWithAllPeers and " : "", g_keychain_changed_notification);
83 else
84 secerror("%snotify_post %s returned: %" PRIu32, syncWithPeers ? "Sent SyncWithAllPeers, " : "", g_keychain_changed_notification, result);
85 }
86
87 static const char * const s3dl_upgrade_sql[] = {
88 /* 0 */
89 "",
90
91 /* 1 */
92 /* Create indices. */
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);",
110
111 /* 2 */
112 "",
113
114 /* 3 */
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;",
122
123 /* 4 */
124 "",
125
126 /* 5 */
127 "",
128
129 /* 6 */
130 "",
131
132 /* 7 */
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;"
138 "DROP TABLE ogenp;"
139 "DROP TABLE oinet;"
140 "DROP TABLE ocert;"
141 "DROP TABLE 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);",
146 };
147
148 struct sql_stages {
149 int pre;
150 int main;
151 int post;
152 bool init_pdmn; // If true do a full export followed by an import of the entire database so all items are re-encoded.
153 };
154
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} */
161
162
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 */
170 };
171
172 static bool sql_run_script(SecDbConnectionRef dbt, int number, CFErrorRef *error)
173 {
174 /* Script -1 == skip this step. */
175 if (number < 0)
176 return true;
177
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;
183 if (number == 0) {
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);
193 });
194 CFReleaseSafe(sql);
195 } else {
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]);
198 }
199 return ok;
200 }
201
202 /* Return the current database version in *version. Returns a
203 SQLITE error. */
204 static bool s3dl_dbt_get_version(SecDbConnectionRef dbt, int *version, CFErrorRef *error)
205 {
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;
213 }
214 return found_version;
215 });
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);
222 }
223 return step_ok;
224 });
225 }
226
227
228 static bool s3dl_dbt_upgrade_from_version(SecDbConnectionRef dbt, int version, CFErrorRef *error)
229 {
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)
234 return ok;
235
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));
242 }
243
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);
249
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);
256 }
257
258 struct sql_stages *script;
259 if (ok) {
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);
263 }
264 if (ok)
265 ok = sql_run_script(dbt, script->pre, error);
266 if (ok)
267 ok = sql_run_script(dbt, script->main, error);
268 if (ok)
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);
274 if (backup) {
275 if (localError) {
276 secerror("Ignoring export error: %@ during upgrade export", localError);
277 CFReleaseNull(localError);
278 }
279 ok = SecServerImportKeychainInPlist(dbt, KEYBAG_NONE,
280 KEYBAG_DEVICE, backup, kSecNoItemFilter, &localError);
281 CFRelease(backup);
282 } else {
283 ok = false;
284
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");
289 } else {
290 secerror("unable to complete upgrade for unknown reason, marking DB as corrupt: %@", localError);
291 SecDbCorrupt(dbt);
292 }
293 }
294
295 if (localError) {
296 if (error && !*error)
297 *error = localError;
298 else
299 CFRelease(localError);
300 }
301 } else if (!ok) {
302 secerror("unable to complete upgrade scripts, marking DB as corrupt: %@", error ? *error : NULL);
303 SecDbCorrupt(dbt);
304 }
305 *commit = ok;
306 }); } else {
307 secerror("unable to complete upgrade scripts, marking DB as corrupt: %@", error ? *error : NULL);
308 SecDbCorrupt(dbt);
309 }
310
311 return ok;
312 }
313
314
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)
319 {
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);
325 //});
326 }
327
328 /* AUDIT[securityd](done):
329 accessGroup (ok) is a caller provided, non NULL CFTypeRef.
330
331 Return true iff accessGroup is allowable according to accessGroups.
332 */
333 static bool accessGroupsAllows(CFArrayRef accessGroups,
334 CFStringRef accessGroup) {
335 /* NULL accessGroups is wildcard. */
336 if (!accessGroups)
337 return true;
338 /* Make sure we have a string. */
339 if (!isString(accessGroup))
340 return false;
341
342 /* Having the special accessGroup "*" allows access to all accessGroups. */
343 CFRange range = { 0, CFArrayGetCount(accessGroups) };
344 if (range.length &&
345 (CFArrayContainsValue(accessGroups, range, accessGroup) ||
346 CFArrayContainsValue(accessGroups, range, CFSTR("*"))))
347 return true;
348
349 return false;
350 }
351
352 bool itemInAccessGroup(CFDictionaryRef item, CFArrayRef accessGroups) {
353 return accessGroupsAllows(accessGroups,
354 CFDictionaryGetValue(item, kSecAttrAccessGroup));
355 }
356
357
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()
362 returns true. */
363 CFDictionaryRef keychain = SecServerExportKeychainPlist(dbt,
364 src_keybag, dest_keybag, kSecBackupableItemFilter,
365 error);
366 if (keychain) {
367 data_out = CFPropertyListCreateData(kCFAllocatorDefault, keychain,
368 kCFPropertyListBinaryFormat_v1_0,
369 0, error);
370 CFRelease(keychain);
371 }
372
373 return data_out;
374 }
375
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, ^{
380 bool ok = false;
381 CFDictionaryRef keychain;
382 keychain = CFPropertyListCreateWithData(kCFAllocatorDefault, data,
383 kCFPropertyListImmutable, NULL,
384 error);
385 if (keychain) {
386 if (isDictionary(keychain)) {
387 ok = SecServerImportKeychainInPlist(dbt, src_keybag,
388 dest_keybag, keychain,
389 kSecBackupableItemFilter,
390 error);
391 } else {
392 ok = SecError(errSecParam, error, CFSTR("import: keychain is not a dictionary"));
393 }
394 CFRelease(keychain);
395 }
396 return ok;
397 });
398 }
399
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);
409 }
410 }
411 return backup;
412 }
413
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))
418 return false;
419
420 /* Import from backup keybag to system keybag. */
421 bool ok = SecServerImportKeychain(dbt, backup_keybag, KEYBAG_DEVICE,
422 backup, error);
423 ok &= ks_close_keybag(backup_keybag, error);
424
425 return ok;
426 }
427
428
429 // MARK - External SPI support code.
430
431 CFStringRef __SecKeychainCopyPath(void) {
432 CFStringRef kcRelPath = NULL;
433 if (use_hwaes()) {
434 kcRelPath = CFSTR("keychain-2.db");
435 } else {
436 kcRelPath = CFSTR("keychain-2-debug.db");
437 }
438
439 CFStringRef kcPath = NULL;
440 CFURLRef kcURL = SecCopyURLForFileInKeychainDirectory(kcRelPath);
441 if (kcURL) {
442 kcPath = CFURLCopyFileSystemPath(kcURL, kCFURLPOSIXPathStyle);
443 CFRelease(kcURL);
444 }
445 return kcPath;
446
447 }
448
449 // MARK; -
450 // MARK: kc_dbhandle init and reset
451
452 SecDbRef SecKeychainDbCreate(CFStringRef path) {
453 return SecDbCreate(path, ^bool (SecDbConnectionRef dbconn, bool didCreate, CFErrorRef *localError) {
454 bool ok;
455 if (didCreate)
456 ok = s3dl_dbt_upgrade_from_version(dbconn, 0, localError);
457 else
458 ok = s3dl_dbt_upgrade(dbconn, localError);
459
460 if (!ok)
461 secerror("Upgrade %sfailed: %@", didCreate ? "from v0 " : "", localError ? *localError : NULL);
462
463 return ok;
464 });
465 }
466
467 static SecDbRef _kc_dbhandle = NULL;
468
469 static void kc_dbhandle_init(void) {
470 SecDbRef oldHandle = _kc_dbhandle;
471 _kc_dbhandle = NULL;
472 CFStringRef dbPath = __SecKeychainCopyPath();
473 if (dbPath) {
474 _kc_dbhandle = SecKeychainDbCreate(dbPath);
475 CFRelease(dbPath);
476 } else {
477 secerror("no keychain path available");
478 }
479 if (oldHandle) {
480 secerror("replaced %@ with %@", oldHandle, _kc_dbhandle);
481 CFRelease(oldHandle);
482 }
483 }
484
485 static dispatch_once_t _kc_dbhandle_once;
486
487 static SecDbRef kc_dbhandle(void) {
488 dispatch_once(&_kc_dbhandle_once, ^{
489 kc_dbhandle_init();
490 });
491 return _kc_dbhandle;
492 }
493
494 /* For whitebox testing only */
495 void kc_dbhandle_reset(void);
496 void kc_dbhandle_reset(void)
497 {
498 __block bool done = false;
499 dispatch_once(&_kc_dbhandle_once, ^{
500 kc_dbhandle_init();
501 done = true;
502 });
503 // TODO: Not thread safe at all! - FOR DEBUGGING ONLY
504 if (!done)
505 kc_dbhandle_init();
506 }
507
508 static SecDbConnectionRef kc_aquire_dbt(bool writeAndRead, CFErrorRef *error) {
509 SecDbRef db = kc_dbhandle();
510 if (db == NULL) {
511 SecError(errSecDataNotAvailable, error, CFSTR("failed to get a db handle"));
512 return NULL;
513 }
514 return SecDbConnectionAquire(db, !writeAndRead, error);
515 }
516
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))
521 {
522 // Make sure we initialize our engines before writing to the keychain
523 if (writeAndRead)
524 SecItemDataSourceFactoryGetDefault();
525
526 bool ok = false;
527 SecDbConnectionRef dbt = kc_aquire_dbt(writeAndRead, error);
528 if (dbt) {
529 ok = perform(dbt);
530 SecDbConnectionRelease(dbt);
531 }
532 return ok;
533 }
534
535 static bool
536 items_matching_issuer_parent(SecDbConnectionRef dbt, CFArrayRef accessGroups,
537 CFDataRef issuer, CFArrayRef issuers, int recurse)
538 {
539 Query *q;
540 CFArrayRef results = NULL;
541 CFIndex i, count;
542 bool found = false;
543
544 if (CFArrayContainsValue(issuers, CFRangeMake(0, CFArrayGetCount(issuers)), issuer))
545 return true;
546
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);
550
551 if (!query)
552 return false;
553
554 CFErrorRef localError = NULL;
555 q = query_create_with_limit(query, kSecMatchUnlimited, &localError);
556 CFRelease(query);
557 if (q) {
558 s3dl_copy_matching(dbt, q, (CFTypeRef*)&results, accessGroups, &localError);
559 query_destroy(q, &localError);
560 }
561 if (localError) {
562 secerror("items matching issuer parent: %@", localError);
563 CFReleaseNull(localError);
564 return false;
565 }
566
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))
572 continue;
573 if (recurse-- > 0)
574 found = items_matching_issuer_parent(dbt, accessGroups, cert_issuer, issuers, recurse);
575 }
576 CFReleaseSafe(results);
577
578 return found;
579 }
580
581 bool match_item(SecDbConnectionRef dbt, Query *q, CFArrayRef accessGroups, CFDictionaryRef item)
582 {
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*/))
586 return false;
587 }
588
589 /* Add future match checks here. */
590
591 return true;
592 }
593
594 /****************************************************************************
595 **************** Beginning of Externally Callable Interface ****************
596 ****************************************************************************/
597
598 #if 0
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;
602 bool ok;
603 if ((ok = perform(&error))) {
604 assert(error == NULL);
605 if (error)
606 secerror("error + success: %@", error);
607 } else {
608 assert(error);
609 OSStatus status = SecErrorGetOSStatus(error);
610 if (status != errSecItemNotFound) // Occurs in normal operation, so exclude
611 secerror("error:[%" PRIdOSStatus "] %@", status, error);
612 if (in_error) {
613 *in_error = error;
614 } else {
615 CFReleaseNull(error);
616 }
617 }
618 return ok;
619 }
620 #endif
621
622 /* AUDIT[securityd](done):
623 query (ok) is a caller provided dictionary, only its cf type has been checked.
624 */
625 static bool
626 SecItemServerCopyMatching(CFDictionaryRef query, CFTypeRef *result,
627 CFArrayRef accessGroups, CFErrorRef *error)
628 {
629 CFIndex ag_count;
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"));
633 }
634
635 if (CFArrayContainsValue(accessGroups, CFRangeMake(0, ag_count), CFSTR("*"))) {
636 /* Having the special accessGroup "*" allows access to all accessGroups. */
637 accessGroups = NULL;
638 }
639
640 bool ok = false;
641 Query *q = query_create_with_limit(query, 1, error);
642 if (q) {
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);
648 } else {
649 CFRetainSafe(accessGroups);
650 }
651
652 query_enable_interactive(q);
653 query_set_caller_access_groups(q, accessGroups);
654
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"));
661 #endif
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) {
668 do {
669 ok = kc_with_dbt(false, error, ^(SecDbConnectionRef dbt) {
670 return s3dl_copy_matching(dbt, q, result, accessGroups, error);
671 });
672 } while (query_needs_authentication(q) && (ok = query_authenticate(q, &error)));
673 }
674
675 CFReleaseSafe(accessGroups);
676 if (!query_destroy(q, error))
677 ok = false;
678 }
679
680 return ok;
681 }
682
683 bool
684 _SecItemCopyMatching(CFDictionaryRef query, CFArrayRef accessGroups, CFTypeRef *result, CFErrorRef *error) {
685 return SecItemServerCopyMatching(query, result, accessGroups, error);
686 }
687
688 /* AUDIT[securityd](done):
689 attributes (ok) is a caller provided dictionary, only its cf type has
690 been checked.
691 */
692 bool
693 _SecItemAdd(CFDictionaryRef attributes, CFArrayRef accessGroups,
694 CFTypeRef *result, CFErrorRef *error)
695 {
696 bool ok = true;
697 CFIndex ag_count;
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"));
701
702 Query *q = query_create_with_limit(attributes, 0, error);
703 if (q) {
704 /* Access group sanity checking. */
705 CFStringRef agrp = (CFStringRef)CFDictionaryGetValue(attributes,
706 kSecAttrAccessGroup);
707
708 CFArrayRef ag = accessGroups;
709 /* Having the special accessGroup "*" allows access to all accessGroups. */
710 if (CFArrayContainsValue(accessGroups, CFRangeMake(0, ag_count), CFSTR("*")))
711 accessGroups = NULL;
712
713 if (agrp) {
714 /* The user specified an explicit access group, validate it. */
715 if (!accessGroupsAllows(accessGroups, agrp))
716 return SecError(errSecNoAccessForItem, error, CFSTR("NoAccessForItem"));
717 } else {
718 agrp = (CFStringRef)CFArrayGetValueAtIndex(ag, 0);
719
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);
723 }
724
725 query_ensure_access_control(q, agrp);
726 query_enable_interactive(q);
727
728 if (q->q_row_id)
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;
733 #endif
734 else if (!q->q_error) {
735 do {
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);
740 });
741 });
742 } while (query_needs_authentication(q) && (ok = query_authenticate(q, &error)));
743 }
744 ok = query_notify_and_destroy(q, ok, error);
745 } else {
746 ok = false;
747 }
748 return ok;
749 }
750
751 /* AUDIT[securityd](done):
752 query (ok) and attributesToUpdate (ok) are a caller provided dictionaries,
753 only their cf types have been checked.
754 */
755 bool
756 _SecItemUpdate(CFDictionaryRef query, CFDictionaryRef attributesToUpdate,
757 CFArrayRef accessGroups, CFErrorRef *error)
758 {
759 CFIndex ag_count;
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"));
763 }
764
765 if (CFArrayContainsValue(accessGroups, CFRangeMake(0, ag_count), CFSTR("*"))) {
766 /* Having the special accessGroup "*" allows access to all accessGroups. */
767 accessGroups = NULL;
768 }
769
770 bool ok = true;
771 Query *q = query_create_with_limit(query, kSecMatchUnlimited, error);
772 if (!q) {
773 ok = false;
774 }
775 if (ok) {
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"));
789 } else {
790 /* Access group sanity checking. */
791 CFStringRef agrp = (CFStringRef)CFDictionaryGetValue(attributesToUpdate,
792 kSecAttrAccessGroup);
793 if (agrp) {
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);
798 }
799 }
800 }
801 }
802 if (ok) {
803 if (!q->q_use_tomb && SOSCCThisDeviceDefinitelyNotActiveInCircle()) {
804 q->q_use_tomb = kCFBooleanFalse;
805 }
806 query_enable_interactive(q);
807 do {
808 ok = kc_with_dbt(true, error, ^(SecDbConnectionRef dbt) {
809 return s3dl_query_update(dbt, q, attributesToUpdate, accessGroups, error);
810 });
811 } while (query_needs_authentication(q) && (ok = query_authenticate(q, &error)));
812 }
813 if (q) {
814 ok = query_notify_and_destroy(q, ok, error);
815 }
816 return ok;
817 }
818
819
820 /* AUDIT[securityd](done):
821 query (ok) is a caller provided dictionary, only its cf type has been checked.
822 */
823 bool
824 _SecItemDelete(CFDictionaryRef query, CFArrayRef accessGroups, CFErrorRef *error)
825 {
826 CFIndex ag_count;
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"));
830 }
831
832 if (CFArrayContainsValue(accessGroups, CFRangeMake(0, ag_count), CFSTR("*"))) {
833 /* Having the special accessGroup "*" allows access to all accessGroups. */
834 accessGroups = NULL;
835 }
836
837 Query *q = query_create_with_limit(query, kSecMatchUnlimited, error);
838 bool ok;
839 if (q) {
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"));
847 else if (q->q_ref)
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"));
851 else {
852 if (!q->q_use_tomb && SOSCCThisDeviceDefinitelyNotActiveInCircle()) {
853 q->q_use_tomb = kCFBooleanFalse;
854 }
855 query_enable_interactive(q);
856 do {
857 ok = kc_with_dbt(true, error, ^(SecDbConnectionRef dbt) {
858 return s3dl_query_delete(dbt, q, accessGroups, error);
859 });
860 } while (query_needs_authentication(q) && (ok = query_authenticate(q, &error)));
861 }
862 ok = query_notify_and_destroy(q, ok, error);
863 } else {
864 ok = false;
865 }
866 return ok;
867 }
868
869
870 /* AUDIT[securityd](done):
871 No caller provided inputs.
872 */
873 static bool
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));
882 });
883 }
884
885 bool
886 _SecItemDeleteAll(CFErrorRef *error) {
887 return SecItemServerDeleteAll(error);
888 }
889
890
891 // MARK: -
892 // MARK: Shared web credentials
893
894 /* constants */
895 #define SEC_CONST_DECL(k,v) CFTypeRef k = (CFTypeRef)(CFSTR(v));
896
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");
902
903 #if !TARGET_IPHONE_SIMULATOR
904 static SWCFlags
905 _SecAppDomainApprovalStatus(CFStringRef appID, CFStringRef fqdn, CFErrorRef *error)
906 {
907 __block SWCFlags flags = kSWCFlags_None;
908
909 #if TARGET_OS_IPHONE
910 CFRetainSafe(appID);
911 CFRetainSafe(fqdn);
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);
918 CFReleaseSafe(fqdn);
919 dispatch_semaphore_signal(semaphore);
920 dispatch_release(semaphore);
921 //secerror("SWCCheckService: inStatus=%d, flags=%0X", inStatus, flags);
922 }))
923 {
924 // wait for the block to complete, as we need its answer
925 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
926 }
927 else // didn't queue the block
928 {
929 CFReleaseSafe(appID);
930 CFReleaseSafe(fqdn);
931 dispatch_release(semaphore);
932 }
933 dispatch_release(semaphore);
934 #else
935 flags |= (kSWCFlag_SiteApproved);
936 #endif
937
938 if (!error) { return flags; }
939 *error = NULL;
940
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);
945 } else {
946 SecError(errSecAuthFailed, error, CFSTR("\"%@\" failed to approve \"%@\""), fqdn, appID);
947 }
948 return flags;
949 }
950
951 // check user approval status
952 if (flags & kSWCFlag_UserDenied) {
953 SecError(errSecAuthFailed, error, CFSTR("User denied access to \"%@\" by \"%@\""), fqdn, appID);
954 }
955 return flags;
956 }
957 #endif
958
959 #if !TARGET_IPHONE_SIMULATOR
960 static bool
961 _SecEntitlementContainsDomainForService(CFArrayRef domains, CFStringRef domain, CFStringRef service)
962 {
963 bool result = false;
964 CFIndex idx, count = (domains) ? CFArrayGetCount(domains) : (CFIndex) 0;
965 if (!count || !domain || !service) {
966 return result;
967 }
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)) {
976 result = true;
977 }
978 CFReleaseSafe(substr);
979 if (result) {
980 break;
981 }
982 }
983 }
984 return result;
985 }
986 #endif
987
988 static bool
989 _SecAddNegativeWebCredential(CFStringRef fqdn, CFStringRef appID, bool forSafari)
990 {
991 bool result = false;
992 if (!fqdn) { return result; }
993
994 #if TARGET_OS_IPHONE
995 // update our database
996 CFRetainSafe(appID);
997 CFRetainSafe(fqdn);
998 if (0 == SWCSetServiceFlags(kSecSharedWebCredentialsService,
999 appID, fqdn, kSWCFlag_ExternalMask, kSWCFlag_UserDenied,
1000 ^void(OSStatus inStatus, SWCFlags inNewFlags){
1001 CFReleaseSafe(appID);
1002 CFReleaseSafe(fqdn);
1003 }))
1004 {
1005 result = true;
1006 }
1007 else // didn't queue the block
1008 {
1009 CFReleaseSafe(appID);
1010 CFReleaseSafe(fqdn);
1011 }
1012 #endif
1013 if (!forSafari) { return result; }
1014
1015 // below this point: create a negative Safari web credential item
1016
1017 CFMutableDictionaryRef attrs = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1018 if (!attrs) { return result; }
1019
1020 CFErrorRef error = NULL;
1021 CFStringRef accessGroup = CFSTR("*");
1022 CFArrayRef accessGroups = CFArrayCreate(kCFAllocatorDefault, (const void **)&accessGroup, 1, &kCFTypeArrayCallBacks);
1023
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);
1030
1031 (void)_SecItemDelete(attrs, accessGroups, &error);
1032 CFReleaseNull(error);
1033
1034 CFDictionaryAddValue(attrs, kSecAttrAccount, kSecSafariPasswordsNotSaved);
1035 CFDictionaryAddValue(attrs, kSecAttrComment, kSecSafariDefaultComment);
1036
1037 CFStringRef label = CFStringCreateWithFormat(kCFAllocatorDefault,
1038 NULL, CFSTR("%@ (%@)"), fqdn, kSecSafariPasswordsNotSaved);
1039 if (label) {
1040 CFDictionaryAddValue(attrs, kSecAttrLabel, label);
1041 CFReleaseSafe(label);
1042 }
1043
1044 UInt8 space = ' ';
1045 CFDataRef data = CFDataCreate(kCFAllocatorDefault, &space, 1);
1046 if (data) {
1047 CFDictionarySetValue(attrs, kSecValueData, data);
1048 CFReleaseSafe(data);
1049 }
1050
1051 CFTypeRef addResult = NULL;
1052 result = _SecItemAdd(attrs, accessGroups, &addResult, &error);
1053
1054 CFReleaseSafe(addResult);
1055 CFReleaseSafe(error);
1056 CFReleaseSafe(attrs);
1057 CFReleaseSafe(accessGroups);
1058
1059 return result;
1060 }
1061
1062 /* Specialized version of SecItemAdd for shared web credentials */
1063 bool
1064 _SecAddSharedWebCredential(CFDictionaryRef attributes,
1065 const audit_token_t *clientAuditToken,
1066 CFStringRef appID,
1067 CFArrayRef domains,
1068 CFTypeRef *result,
1069 CFErrorRef *error) {
1070
1071 CFStringRef fqdn = CFDictionaryGetValue(attributes, kSecAttrServer);
1072 CFStringRef account = CFDictionaryGetValue(attributes, kSecAttrAccount);
1073 #if TARGET_OS_IPHONE
1074 CFStringRef password = CFDictionaryGetValue(attributes, kSecSharedPassword);
1075 #else
1076 CFStringRef password = CFDictionaryGetValue(attributes, CFSTR("spwd"));
1077 #endif
1078 CFStringRef accessGroup = CFSTR("*");
1079 CFArrayRef accessGroups = NULL;
1080 CFMutableDictionaryRef query = NULL, attrs = NULL;
1081 SInt32 port = -1;
1082 bool ok = false, update = false;
1083 //bool approved = false;
1084
1085 // check autofill enabled status
1086 if (!swca_autofill_enabled(clientAuditToken)) {
1087 SecError(errSecBadReq, error, CFSTR("Autofill is not enabled in Safari settings"));
1088 goto cleanup;
1089 }
1090
1091 // parse fqdn with CFURL here, since it could be specified as domain:port
1092 if (fqdn) {
1093 CFRetainSafe(fqdn);
1094 CFStringRef urlStr = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@%@"), kSecSharedCredentialUrlScheme, fqdn);
1095 if (urlStr) {
1096 CFURLRef url = CFURLCreateWithString(kCFAllocatorDefault, urlStr, nil);
1097 if (url) {
1098 CFStringRef hostname = CFURLCopyHostName(url);
1099 if (hostname) {
1100 CFReleaseSafe(fqdn);
1101 fqdn = hostname;
1102 port = CFURLGetPortNumber(url);
1103 }
1104 CFReleaseSafe(url);
1105 }
1106 CFReleaseSafe(urlStr);
1107 }
1108 }
1109
1110 if (!account) {
1111 SecError(errSecParam, error, CFSTR("No account provided"));
1112 goto cleanup;
1113 }
1114 if (!fqdn) {
1115 SecError(errSecParam, error, CFSTR("No domain provided"));
1116 goto cleanup;
1117 }
1118
1119 #if TARGET_IPHONE_SIMULATOR
1120 secerror("app/site association entitlements not checked in Simulator");
1121 #else
1122 OSStatus status = errSecMissingEntitlement;
1123 // validate that fqdn is part of caller's shared credential domains entitlement
1124 if (!appID) {
1125 SecError(status, error, CFSTR("Missing application-identifier entitlement"));
1126 goto cleanup;
1127 }
1128 if (_SecEntitlementContainsDomainForService(domains, fqdn, kSecSharedWebCredentialsService)) {
1129 status = errSecSuccess;
1130 }
1131 if (errSecSuccess != status) {
1132 CFStringRef msg = CFStringCreateWithFormat(kCFAllocatorDefault, NULL,
1133 CFSTR("%@ not found in %@ entitlement"), fqdn, kSecEntitlementAssociatedDomains);
1134 if (!msg) {
1135 msg = CFRetain(CFSTR("Requested domain not found in entitlement"));
1136 }
1137 SecError(status, error, CFSTR("%@"), msg);
1138 CFReleaseSafe(msg);
1139 goto cleanup;
1140 }
1141 #endif
1142
1143 #if TARGET_IPHONE_SIMULATOR
1144 secerror("Ignoring app/site approval state in the Simulator.");
1145 #else
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)) {
1150 goto cleanup;
1151 }
1152 #endif
1153
1154 // give ourselves access to see matching items for kSecSafariAccessGroup
1155 accessGroups = CFArrayCreate(kCFAllocatorDefault, (const void **)&accessGroup, 1, &kCFTypeArrayCallBacks);
1156
1157 // create lookup query
1158 query = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1159 if (!query) {
1160 SecError(errSecAllocate, error, CFSTR("Unable to create query dictionary"));
1161 goto cleanup;
1162 }
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);
1168
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);
1174 if (ok) {
1175 SecError(errSecDuplicateItem, error, CFSTR("Item already exists for this server"));
1176 goto cleanup;
1177 }
1178
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);
1186 }
1187
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);
1194 if (update) {
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);
1200
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); });
1205 if (ok) {
1206 ok = _SecItemUpdate(query, attrs, accessGroups, error);
1207 }
1208 }
1209 else {
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); });
1214 if (ok) {
1215 ok = _SecItemDelete(query, accessGroups, error);
1216 }
1217 }
1218 if (ok) {
1219 CFReleaseNull(*error);
1220 }
1221 goto cleanup;
1222 }
1223 CFReleaseNull(*result);
1224 CFReleaseNull(*error);
1225
1226 // password does not exist, so prepare to add it
1227 if (true) {
1228 CFStringRef label = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@ (%@)"), fqdn, account);
1229 if (label) {
1230 CFDictionaryAddValue(query, kSecAttrLabel, label);
1231 CFReleaseSafe(label);
1232 }
1233 // NOTE: we always expect to use HTTPS for web forms.
1234 CFDictionaryAddValue(query, kSecAttrProtocol, kSecAttrProtocolHTTPS);
1235
1236 CFDataRef credential = CFStringCreateExternalRepresentation(kCFAllocatorDefault, password, kCFStringEncodingUTF8, 0);
1237 CFDictionarySetValue(query, kSecValueData, credential);
1238 CFReleaseSafe(credential);
1239 CFDictionarySetValue(query, kSecAttrComment, kSecSafariDefaultComment);
1240
1241 CFReleaseSafe(accessGroups);
1242 accessGroups = CFArrayCreate(kCFAllocatorDefault, (const void **)&kSecSafariAccessGroup, 1, &kCFTypeArrayCallBacks);
1243
1244 // mark the item as created by this function
1245 const int32_t creator_value = 'swca';
1246 CFNumberRef creator = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &creator_value);
1247 if (creator) {
1248 CFDictionarySetValue(query, kSecAttrCreator, creator);
1249 CFReleaseSafe(creator);
1250 ok = true;
1251 }
1252 else {
1253 // confirm the add
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); });
1257 }
1258 }
1259 if (ok) {
1260 ok = _SecItemAdd(query, accessGroups, result, error);
1261 }
1262
1263 cleanup:
1264 #if 0 /* debugging */
1265 {
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);
1269 }
1270 #else
1271 (void)update;
1272 #endif
1273 CFReleaseSafe(attrs);
1274 CFReleaseSafe(query);
1275 CFReleaseSafe(accessGroups);
1276 CFReleaseSafe(fqdn);
1277 return ok;
1278 }
1279
1280 /* Specialized version of SecItemCopyMatching for shared web credentials */
1281 bool
1282 _SecCopySharedWebCredential(CFDictionaryRef query,
1283 const audit_token_t *clientAuditToken,
1284 CFStringRef appID,
1285 CFArrayRef domains,
1286 CFTypeRef *result,
1287 CFErrorRef *error) {
1288
1289 CFMutableArrayRef credentials = NULL;
1290 CFMutableArrayRef foundItems = NULL;
1291 CFMutableArrayRef fqdns = NULL;
1292 CFArrayRef accessGroups = NULL;
1293 CFStringRef fqdn = NULL;
1294 CFStringRef account = NULL;
1295 CFIndex idx, count;
1296 SInt32 port = -1;
1297 bool ok = false;
1298
1299 require_quiet(result, cleanup);
1300 credentials = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
1301 foundItems = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
1302 fqdns = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
1303
1304 // give ourselves access to see matching items for kSecSafariAccessGroup
1305 CFStringRef accessGroup = CFSTR("*");
1306 accessGroups = CFArrayCreate(kCFAllocatorDefault, (const void **)&accessGroup, 1, &kCFTypeArrayCallBacks);
1307
1308 // On input, the query dictionary contains optional fqdn and account entries.
1309 fqdn = CFDictionaryGetValue(query, kSecAttrServer);
1310 account = CFDictionaryGetValue(query, kSecAttrAccount);
1311
1312 // Check autofill enabled status
1313 if (!swca_autofill_enabled(clientAuditToken)) {
1314 SecError(errSecBadReq, error, CFSTR("Autofill is not enabled in Safari settings"));
1315 goto cleanup;
1316 }
1317
1318 // Check fqdn; if NULL, add domains from caller's entitlement.
1319 if (fqdn) {
1320 CFArrayAppendValue(fqdns, fqdn);
1321 }
1322 else if (domains) {
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);
1332 if (fqdn) {
1333 CFArrayAppendValue(fqdns, fqdn);
1334 CFRelease(fqdn);
1335 }
1336 }
1337 }
1338 }
1339 count = CFArrayGetCount(fqdns);
1340 if (count < 1) {
1341 SecError(errSecParam, error, CFSTR("No domain provided"));
1342 goto cleanup;
1343 }
1344
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);
1350 CFRetainSafe(fqdn);
1351 port = -1;
1352
1353 // Parse the fqdn for a possible port specifier.
1354 if (fqdn) {
1355 CFStringRef urlStr = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@%@"), kSecSharedCredentialUrlScheme, fqdn);
1356 if (urlStr) {
1357 CFURLRef url = CFURLCreateWithString(kCFAllocatorDefault, urlStr, nil);
1358 if (url) {
1359 CFStringRef hostname = CFURLCopyHostName(url);
1360 if (hostname) {
1361 CFReleaseSafe(fqdn);
1362 fqdn = hostname;
1363 port = CFURLGetPortNumber(url);
1364 }
1365 CFReleaseSafe(url);
1366 }
1367 CFReleaseSafe(urlStr);
1368 }
1369 }
1370
1371 #if TARGET_IPHONE_SIMULATOR
1372 secerror("app/site association entitlements not checked in Simulator");
1373 #else
1374 OSStatus status = errSecMissingEntitlement;
1375 if (!appID) {
1376 SecError(status, error, CFSTR("Missing application-identifier entitlement"));
1377 CFReleaseSafe(fqdn);
1378 goto cleanup;
1379 }
1380 // validate that fqdn is part of caller's entitlement
1381 if (_SecEntitlementContainsDomainForService(domains, fqdn, kSecSharedWebCredentialsService)) {
1382 status = errSecSuccess;
1383 }
1384 if (errSecSuccess != status) {
1385 CFStringRef msg = CFStringCreateWithFormat(kCFAllocatorDefault, NULL,
1386 CFSTR("%@ not found in %@ entitlement"), fqdn, kSecEntitlementAssociatedDomains);
1387 if (!msg) {
1388 msg = CFRetain(CFSTR("Requested domain not found in entitlement"));
1389 }
1390 SecError(status, error, CFSTR("%@"), msg);
1391 CFReleaseSafe(msg);
1392 CFReleaseSafe(fqdn);
1393 goto cleanup;
1394 }
1395 #endif
1396
1397 attrs = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1398 if (!attrs) {
1399 SecError(errSecAllocate, error, CFSTR("Unable to create query dictionary"));
1400 CFReleaseSafe(fqdn);
1401 goto cleanup;
1402 }
1403 CFDictionaryAddValue(attrs, kSecClass, kSecClassInternetPassword);
1404 CFDictionaryAddValue(attrs, kSecAttrAccessGroup, kSecSafariAccessGroup);
1405 CFDictionaryAddValue(attrs, kSecAttrAuthenticationType, kSecAttrAuthenticationTypeHTMLForm);
1406 CFDictionaryAddValue(attrs, kSecAttrServer, fqdn);
1407 if (account) {
1408 CFDictionaryAddValue(attrs, kSecAttrAccount, account);
1409 }
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);
1415 }
1416 CFDictionaryAddValue(attrs, kSecAttrSynchronizable, kCFBooleanTrue);
1417 CFDictionaryAddValue(attrs, kSecMatchLimit, kSecMatchLimitAll);
1418 CFDictionaryAddValue(attrs, kSecReturnAttributes, kCFBooleanTrue);
1419 CFDictionaryAddValue(attrs, kSecReturnData, kCFBooleanTrue);
1420
1421 ok = _SecItemCopyMatching(attrs, accessGroups, (CFTypeRef*)&items, error);
1422 if (count > 1) {
1423 // ignore interim error since we have multiple domains to search
1424 CFReleaseNull(*error);
1425 }
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;
1430 #else
1431 // get approval status for this app/domain pair
1432 SWCFlags flags = _SecAppDomainApprovalStatus(appID, fqdn, error);
1433 if (count > 1) {
1434 // ignore interim error since we have multiple domains to check
1435 CFReleaseNull(*error);
1436 }
1437 bool approved = (flags & kSWCFlag_SiteApproved);
1438 #endif
1439 if (approved) {
1440 CFArrayAppendArray(foundItems, items, CFRangeMake(0, CFArrayGetCount(items)));
1441 }
1442 }
1443 CFReleaseSafe(items);
1444 CFReleaseSafe(attrs);
1445 CFReleaseSafe(fqdn);
1446 }
1447
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)
1454 // Optional keys:
1455 // key: kSecAttrPort value: CFNumberRef (the port number, if non-standard for https)
1456
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);
1467 if (srvr) {
1468 CFDictionaryAddValue(newdict, kSecAttrServer, srvr);
1469 }
1470 if (acct) {
1471 CFDictionaryAddValue(newdict, kSecAttrAccount, acct);
1472 }
1473 if (pnum) {
1474 SInt16 pval = -1;
1475 if (CFNumberGetValue(pnum, kCFNumberSInt16Type, &pval) &&
1476 (pval < -1 || pval > 0)) {
1477 CFDictionaryAddValue(newdict, kSecAttrPort, pnum);
1478 }
1479 }
1480 if (data) {
1481 CFStringRef password = CFStringCreateFromExternalRepresentation(kCFAllocatorDefault, data, kCFStringEncodingUTF8);
1482 if (password) {
1483 #if TARGET_OS_IPHONE
1484 CFDictionaryAddValue(newdict, kSecSharedPassword, password);
1485 #else
1486 CFDictionaryAddValue(newdict, CFSTR("spwd"), password);
1487 #endif
1488 CFReleaseSafe(password);
1489 }
1490 }
1491 if (icmt && CFEqual(icmt, kSecSafariDefaultComment)) {
1492 CFArrayInsertValueAtIndex(credentials, 0, newdict);
1493 } else {
1494 CFArrayAppendValue(credentials, newdict);
1495 }
1496 }
1497 CFReleaseSafe(newdict);
1498 }
1499
1500 if (count) {
1501
1502 ok = false;
1503
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);
1512 #else
1513 CFDictionaryRemoveValue(newdict, CFSTR("spwd"));
1514 #endif
1515 CFArrayAppendValue(items, newdict);
1516 CFReleaseSafe(newdict);
1517 }
1518
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);
1522 if (selected) {
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);
1532
1533 if (!srvr || !srvr1 || !CFEqual(srvr, srvr1)) continue;
1534 if (!acct || !acct1 || !CFEqual(acct, acct1)) continue;
1535 if ((pnum && pnum1) && !CFEqual(pnum, pnum1)) continue;
1536
1537 // we have a match!
1538 CFReleaseSafe(selected);
1539 CFRetainSafe(dict);
1540 selected = dict;
1541 ok = true;
1542 break;
1543 }
1544 }
1545 CFReleaseSafe(items);
1546 CFArrayRemoveAllValues(credentials);
1547 if (selected && ok) {
1548 #if TARGET_OS_IPHONE
1549 fqdn = CFDictionaryGetValue(selected, kSecAttrServer);
1550 #endif
1551 CFArrayAppendValue(credentials, selected);
1552 }
1553
1554 #if 0
1555 // confirm the access
1556 ok = swca_confirm_operation(swca_copy_request_id, clientAuditToken, query, error,
1557 ^void (CFStringRef fqdn) { _SecAddNegativeWebCredential(fqdn, appID, false); });
1558 #endif
1559 if (ok) {
1560 #if TARGET_OS_IPHONE
1561 // register confirmation with database
1562 CFRetainSafe(appID);
1563 CFRetainSafe(fqdn);
1564 if (0 != SWCSetServiceFlags(kSecSharedWebCredentialsService,
1565 appID, fqdn, kSWCFlag_ExternalMask, kSWCFlag_UserApproved,
1566 ^void(OSStatus inStatus, SWCFlags inNewFlags){
1567 CFReleaseSafe(appID);
1568 CFReleaseSafe(fqdn);
1569 }))
1570 {
1571 // we didn't queue the block
1572 CFReleaseSafe(appID);
1573 CFReleaseSafe(fqdn);
1574 }
1575 #endif
1576 }
1577 CFReleaseSafe(selected);
1578 }
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"));
1582 }
1583
1584 cleanup:
1585 if (!ok) {
1586 CFArrayRemoveAllValues(credentials);
1587 }
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);
1594 #endif
1595 return ok;
1596 }
1597
1598 // MARK: -
1599 // MARK: Keychain backup
1600
1601 CF_RETURNS_RETAINED CFDataRef
1602 _SecServerKeychainBackup(CFDataRef keybag, CFDataRef passcode, CFErrorRef *error) {
1603 CFDataRef backup;
1604 SecDbConnectionRef dbt = SecDbConnectionAquire(kc_dbhandle(), false, error);
1605
1606 if (!dbt)
1607 return NULL;
1608
1609 if (keybag == NULL && passcode == NULL) {
1610 #if USE_KEYSTORE
1611 backup = SecServerExportKeychain(dbt, KEYBAG_DEVICE, backup_keybag_handle, error);
1612 #else /* !USE_KEYSTORE */
1613 SecError(errSecParam, error, CFSTR("Why are you doing this?"));
1614 backup = NULL;
1615 #endif /* USE_KEYSTORE */
1616 } else {
1617 backup = SecServerKeychainBackup(dbt, keybag, passcode, error);
1618 }
1619
1620 SecDbConnectionRelease(dbt);
1621
1622 return backup;
1623 }
1624
1625 bool
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"));
1629
1630 __block bool ok = true;
1631 ok &= SecDbPerformWrite(kc_dbhandle(), error, ^(SecDbConnectionRef dbconn) {
1632 ok = SecServerKeychainRestore(dbconn, backup, keybag, passcode, error);
1633 });
1634
1635 if (ok) {
1636 SecKeychainChanged(true);
1637 }
1638
1639 return ok;
1640 }
1641
1642 // MARK: -
1643 // MARK: SecItemDataSource
1644
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());
1649 }
1650
1651 /* AUDIT[securityd]:
1652 args_in (ok) is a caller provided, CFDictionaryRef.
1653 */
1654
1655 CF_RETURNS_RETAINED CFArrayRef
1656 _SecServerKeychainSyncUpdateKeyParameter(CFDictionaryRef updates, CFErrorRef *error) {
1657 // This never fails, trust us!
1658 return SOSCCHandleUpdateKeyParameter(updates);
1659 }
1660
1661
1662 CF_RETURNS_RETAINED CFArrayRef
1663 _SecServerKeychainSyncUpdateCircle(CFDictionaryRef updates, CFErrorRef *error) {
1664 // This never fails, trust us!
1665 return SOSCCHandleUpdateCircle(updates);
1666 }
1667
1668 CF_RETURNS_RETAINED CFArrayRef
1669 _SecServerKeychainSyncUpdateMessage(CFDictionaryRef updates, CFErrorRef *error) {
1670 // This never fails, trust us!
1671 return SOSCCHandleUpdateMessage(updates);
1672 }
1673
1674
1675 //
1676 // Truthiness in the cloud backup/restore support.
1677 //
1678
1679 static CFDictionaryRef
1680 _SecServerCopyTruthInTheCloud(CFDataRef keybag, CFDataRef password,
1681 CFDictionaryRef backup, CFErrorRef *error)
1682 {
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))
1687 return backup_new;
1688
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);
1694 if (ds) {
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);
1699 if (!mnow) {
1700 mnow = SOSDataSourceCopyManifest(ds, error);
1701 }
1702 if (!mnow) {
1703 CFReleaseNull(backup_new);
1704 secerror("failed to obtain manifest for keychain: %@", error ? *error : NULL);
1705 } else {
1706 SOSManifestDiff(mold, mnow, &mdelete, &madd, error);
1707 }
1708
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);
1714 });
1715
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;
1721 if (!object) {
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));
1731 } else {
1732 // Stop iterating and propagate out all other errors.
1733 *stop = true;
1734 *error = localError;
1735 CFReleaseNull(backup_new);
1736 }
1737 } else {
1738 // TODO: Should we skip tombstones here?
1739 CFStringRef key = CFDataCopyHexString(digest_data);
1740 CFDictionarySetValue(backup_new, key, value);
1741 CFReleaseSafe(key);
1742 }
1743 CFReleaseSafe(digest_data);
1744 CFReleaseSafe(value);
1745 }) || CFReleaseNull(backup_new);
1746
1747 if (dvdel.count) {
1748 struct SOSDigestVector dvadd = SOSDigestVectorInit;
1749 if (!SOSEngineUpdateLocalManifest(engine, kSOSDataSourceSOSTransaction, &dvdel, &dvadd, error)) {
1750 CFReleaseNull(backup_new);
1751 }
1752 SOSDigestVectorFree(&dvdel);
1753 }
1754
1755 SOSDataSourceRelease(ds, error) || CFReleaseNull(backup_new);
1756 }
1757
1758 CFReleaseSafe(mold);
1759 CFReleaseSafe(mnow);
1760 CFReleaseSafe(madd);
1761 CFReleaseSafe(mdelete);
1762 ks_close_keybag(bag_handle, error) || CFReleaseNull(backup_new);
1763
1764 return backup_new;
1765 }
1766
1767 static bool
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))
1772 return false;
1773
1774 SOSManifestRef mbackup = SOSCreateManifestWithBackup(backup_in, error);
1775 if (mbackup) {
1776 SOSDataSourceFactoryRef dsf = SecItemDataSourceFactoryGetDefault();
1777 SOSDataSourceRef ds = dsf->create_datasource(dsf, kSecAttrAccessibleWhenUnlocked, error);
1778 if (ds) {
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);
1783
1784 // Don't delete everything in datasource not in backup.
1785
1786 // Add items from the backup
1787 SOSManifestForEach(madd, ^void(CFDataRef e, bool *stop) {
1788 CFDictionaryRef item = NULL;
1789 CFStringRef sha1 = CFDataCopyHexString(e);
1790 if (sha1) {
1791 item = CFDictionaryGetValue(backup_in, sha1);
1792 CFRelease(sha1);
1793 }
1794 if (item) {
1795 CFErrorRef localError = NULL;
1796
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);
1802 } else {
1803 if (status == errSecInteractionNotAllowed)
1804 *stop = true;
1805 // Propagate the first other error upwards (causing the restore to fail).
1806 secerror("restore %@ failed %@", item, localError);
1807 ok = false;
1808 if (error && !*error) {
1809 *error = localError;
1810 localError = NULL;
1811 }
1812 }
1813 CFReleaseSafe(localError);
1814 }
1815 }
1816 });
1817 ok &= SOSDataSourceRelease(ds, error);
1818 CFReleaseNull(mdelete);
1819 CFReleaseNull(madd);
1820 CFReleaseNull(mnow);
1821 });
1822 } else {
1823 ok = false;
1824 }
1825 CFRelease(mbackup);
1826 }
1827
1828 ok &= ks_close_keybag(bag_handle, error);
1829
1830 return ok;
1831 }
1832
1833
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));
1839
1840 return _SecServerCopyTruthInTheCloud(keybag, password, backup, error);
1841
1842 errOut:
1843 return NULL;
1844 }
1845
1846 bool
1847 _SecServerRestoreSyncable(CFDictionaryRef backup, CFDataRef keybag, CFDataRef password, CFErrorRef *error) {
1848 bool ok;
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));
1851 if (password) {
1852 require_action_quiet(isData(password), errOut, ok = SecError(errSecParam, error, CFSTR("password not a data")));
1853 }
1854
1855 ok = _SecServerRestoreTruthInTheCloud(keybag, password, backup, error);
1856
1857 errOut:
1858 return ok;
1859 }
1860
1861 bool _SecServerRollKeys(bool force, CFErrorRef *error) {
1862 #if USE_KEYSTORE
1863 uint32_t keystore_generation_status = 0;
1864 if (aks_generation(KEYBAG_DEVICE, generation_noop, &keystore_generation_status))
1865 return false;
1866 uint32_t current_generation = keystore_generation_status & generation_current;
1867
1868 return kc_with_dbt(true, error, ^(SecDbConnectionRef dbt) {
1869 bool up_to_date = s3dl_dbt_keys_current(dbt, current_generation, NULL);
1870
1871 if (force && !up_to_date) {
1872 up_to_date = s3dl_dbt_update_keys(dbt, error);
1873 if (up_to_date) {
1874 secerror("Completed roll keys.");
1875 up_to_date = s3dl_dbt_keys_current(dbt, current_generation, NULL);
1876 }
1877 if (!up_to_date)
1878 secerror("Failed to roll keys.");
1879 }
1880 return up_to_date;
1881 });
1882 #else
1883 return true;
1884 #endif
1885 }