]> git.saurik.com Git - apple/security.git/blob - OSX/sec/securityd/SecItemServer.c
2c3feb016e5c849cbcbc23111861433a403df099
[apple/security.git] / OSX / sec / securityd / SecItemServer.c
1 /*
2 * Copyright (c) 2006-2015 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 <Security/SecureObjectSync/SOSChangeTracker.h>
41 #include <Security/SecureObjectSync/SOSDigestVector.h>
42 #include <Security/SecureObjectSync/SOSViews.h>
43
44 // TODO: Make this include work on both platforms. rdar://problem/16526848
45 #if TARGET_OS_EMBEDDED
46 #include <Security/SecEntitlements.h>
47 #else
48 /* defines from <Security/SecEntitlements.h> */
49 #define kSecEntitlementAssociatedDomains CFSTR("com.apple.developer.associated-domains")
50 #define kSecEntitlementPrivateAssociatedDomains CFSTR("com.apple.private.associated-domains")
51 #endif
52
53 #include <utilities/array_size.h>
54 #include <utilities/SecFileLocations.h>
55 #include <Security/SecuritydXPC.h>
56 #include "swcagent_client.h"
57
58 #if TARGET_OS_IPHONE && !TARGET_OS_NANO
59 #include <dlfcn.h>
60 #include <SharedWebCredentials/SharedWebCredentials.h>
61
62 typedef OSStatus (*SWCCheckService_f)(CFStringRef service, CFStringRef appID, CFStringRef domain, SWCCheckServiceCompletion_b completion);
63 typedef OSStatus (*SWCSetServiceFlags_f)(CFStringRef service, CFStringRef appID, CFStringRef domain, SWCFlags mask, SWCFlags flags, SWCSetServiceFlagsCompletion_b completion);
64 #else
65 typedef uint32_t SWCFlags;
66 #define kSWCFlags_None 0
67 #define kSWCFlag_Pending ( 1U << 0 )
68 #define kSWCFlag_SiteApproved ( 1U << 1 )
69 #define kSWCFlag_SiteDenied ( 1U << 2 )
70 #define kSWCFlag_UserApproved ( 1U << 3 )
71 #define kSWCFlag_UserDenied ( 1U << 4 )
72 #define kSWCFlag_ExternalMask ( kSWCFlag_UserApproved | kSWCFlag_UserDenied )
73 #endif
74
75 /* Changed the name of the keychain changed notification, for testing */
76 static const char *g_keychain_changed_notification = kSecServerKeychainChangedNotification;
77
78 void SecItemServerSetKeychainChangedNotification(const char *notification_name)
79 {
80 g_keychain_changed_notification = notification_name;
81 }
82
83 void SecKeychainChanged(bool syncWithPeers) {
84 uint32_t result = notify_post(g_keychain_changed_notification);
85 if (syncWithPeers)
86 SOSCCSyncWithAllPeers();
87 if (result == NOTIFY_STATUS_OK)
88 secnotice("item", "Sent %s%s", syncWithPeers ? "SyncWithAllPeers and " : "", g_keychain_changed_notification);
89 else
90 secerror("%snotify_post %s returned: %" PRIu32, syncWithPeers ? "Sent SyncWithAllPeers, " : "", g_keychain_changed_notification, result);
91 }
92
93 /* Return the current database version in *version. */
94 static bool SecKeychainDbGetVersion(SecDbConnectionRef dbt, int *version, CFErrorRef *error)
95 {
96 __block bool ok = false;
97 SecDbQueryRef query = NULL;
98 __block CFNumberRef versionNumber = NULL;
99 __block CFErrorRef localError = NULL;
100
101 require_quiet(query = query_create(&tversion_class, NULL, &localError), out);
102 require_quiet(SecDbItemSelect(query, dbt, &localError, ^bool(const SecDbAttr *attr) {
103 // Bind all attributes.
104 return true;
105 }, ^bool(const SecDbAttr *attr) {
106 // No filtering.
107 return false;
108 }, NULL, NULL, ^(SecDbItemRef item, bool *stop) {
109 versionNumber = copyNumber(SecDbItemGetValue(item, tversion_class.attrs[0], &localError));
110 *stop = true;
111 }), out);
112
113 require_action_quiet(versionNumber != NULL && CFNumberGetValue(versionNumber, kCFNumberIntType, version), out,
114 // We have a tversion table but we didn't find a single version
115 // value, now what? I suppose we pretend the db is corrupted
116 // since this isn't supposed to ever happen.
117 SecDbError(SQLITE_CORRUPT, error, CFSTR("Failed to read version table"));
118 secwarning("tversion read error: %@", error ? *error : NULL));
119 ok = true;
120
121 out:
122 if (!ok && CFErrorGetCode(localError) == SQLITE_ERROR) {
123 // Most probably means that the version table does not exist at all.
124 // TODO: Use "SELECT name FROM sqlite_master WHERE type='table' AND name='tversion'" to detect tversion presence.
125 CFReleaseSafe(localError);
126 version = 0;
127 ok = true;
128 }
129 if (query)
130 query_destroy(query, NULL);
131 CFReleaseSafe(versionNumber);
132 return ok || CFErrorPropagate(localError, error);
133 }
134
135 // Goes through all tables represented by old_schema and tries to migrate all items from them into new (current version) tables.
136 static bool SecKeychainDbUpgradeFromSchema(SecDbConnectionRef dbt, const SecDbSchema *oldSchema, bool *inProgress, CFErrorRef *error) {
137 __block bool ok = true;
138 const SecDbSchema *newSchema = kc_schemas[0];
139 SecDbClass const *const *oldClass;
140 SecDbClass const *const *newClass;
141 SecDbQueryRef query = NULL;
142 CFMutableStringRef sql = NULL;
143
144 // Rename existing tables to old names, as present in old schemas.
145 sql = CFStringCreateMutable(NULL, 0);
146 for (oldClass = oldSchema->classes, newClass = newSchema->classes;
147 *oldClass != NULL && *newClass != NULL; oldClass++, newClass++) {
148 if (!CFEqual((*oldClass)->name, (*newClass)->name)) {
149 CFStringAppendFormat(sql, NULL, CFSTR("ALTER TABLE %@ RENAME TO %@;"),
150 (*newClass)->name, (*oldClass)->name);
151 } else {
152 CFStringAppendFormat(sql, NULL, CFSTR("DROP TABLE %@;"), (*oldClass)->name);
153 }
154 }
155 require_quiet(ok &= SecDbExec(dbt, sql, error), out);
156
157 // Create tables for new schema.
158 require_quiet(ok &= SecItemDbCreateSchema(dbt, newSchema, error), out);
159 // Go through all classes of current schema to transfer all items to new tables.
160 for (oldClass = oldSchema->classes, newClass = newSchema->classes;
161 *oldClass != NULL && *newClass != NULL; oldClass++, newClass++) {
162 if (CFEqual((*oldClass)->name, (*newClass)->name))
163 continue;
164
165 // Prepare query to iterate through all items in cur_class.
166 if (query != NULL)
167 query_destroy(query, NULL);
168 require_quiet(query = query_create(*oldClass, NULL, error), out);
169
170 ok &= SecDbItemSelect(query, dbt, error, ^bool(const SecDbAttr *attr) {
171 // We are interested in all attributes which are physically present in the DB.
172 return (attr->flags & kSecDbInFlag) != 0;
173 }, ^bool(const SecDbAttr *attr) {
174 // No filtering please.
175 return false;
176 }, NULL, NULL, ^(SecDbItemRef item, bool *stop) {
177 CFErrorRef localError = NULL;
178
179 // Switch item to the new class.
180 item->class = *newClass;
181
182 // Decrypt the item.
183 if (SecDbItemEnsureDecrypted(item, &localError)) {
184 // Delete SHA1 field from the item, so that it is newly recalculated before storing
185 // the item into the new table.
186 require_quiet(ok &= SecDbItemSetValue(item, SecDbClassAttrWithKind(item->class, kSecDbSHA1Attr, error),
187 kCFNull, error), out);
188 } else {
189 OSStatus status = SecErrorGetOSStatus(localError);
190
191 // Items producing errSecDecode are silently dropped - they are not decodable and lost forever.
192 require_quiet(status != errSecDecode, out);
193
194 // errSecAuthNeeded means that it is an ACL-based item which requires authentication (or at least
195 // ACM context, which we do not have). Other errors should abort the migration completely.
196 require_action_quiet(status == errSecAuthNeeded || status == errSecInteractionNotAllowed, out,
197 ok &= CFErrorPropagate(localError, error); localError = NULL);
198
199 // If we've hit item which could not be decoded because of locked keybag, store it into
200 // new tables so that accessing it fails with errSecInteractionNotAllowed instead of errSecItemNotFound.
201 // Next connection to the DB with opened keybag will properly decode and replace the item.
202 if (status == errSecInteractionNotAllowed) {
203 *inProgress = true;
204 }
205
206 // Leave item encrypted, do not ever try to decrypt it since it will fail.
207 item->_edataState = kSecDbItemAlwaysEncrypted;
208 }
209
210 // Insert new item into the new table.
211 if (!SecDbItemInsert(item, dbt, &localError)) {
212 secerror("item: %@ insert during upgrade: %@", item, localError);
213 ok &= CFErrorPropagate(localError, error); localError = NULL;
214 }
215
216 out:
217 CFReleaseSafe(localError);
218 *stop = !ok;
219 });
220 require_quiet(ok, out);
221 }
222
223 // Remove old tables from the DB.
224 CFAssignRetained(sql, CFStringCreateMutable(NULL, 0));
225 for (oldClass = oldSchema->classes, newClass = newSchema->classes;
226 *oldClass != NULL && *newClass != NULL; oldClass++, newClass++) {
227 if (!CFEqual((*oldClass)->name, (*newClass)->name)) {
228 CFStringAppendFormat(sql, NULL, CFSTR("DROP TABLE %@;"), (*oldClass)->name);
229 }
230 }
231 require_quiet(ok &= SecDbExec(dbt, sql, error), out);
232
233 out:
234 if (query != NULL) {
235 query_destroy(query, NULL);
236 }
237 CFReleaseSafe(sql);
238 return ok;
239 }
240
241 // Goes through all tables represented by old_schema and tries to migrate all items from them into new (current version) tables.
242 static bool SecKeychainDbUpgradeUnlockedItems(SecDbConnectionRef dbt, bool *inProgress, CFErrorRef *error) {
243 __block bool ok = true;
244 SecDbQueryRef query = NULL;
245
246 // Go through all classes in new schema
247 const SecDbSchema *newSchema = kc_schemas[0];
248 for (const SecDbClass *const *class = newSchema->classes; *class != NULL && !*inProgress; class++) {
249 const SecDbAttr *pdmn = SecDbClassAttrWithKind(*class, kSecDbAccessAttr, error);
250 if (pdmn == nil) {
251 continue;
252 }
253
254 // Prepare query to go through all non-DK|DKU items
255 if (query != NULL) {
256 query_destroy(query, NULL);
257 }
258 require_action_quiet(query = query_create(*class, NULL, error), out, ok = false);
259 ok = SecDbItemSelect(query, dbt, error, NULL, ^bool(const SecDbAttr *attr) {
260 // No simple per-attribute filtering.
261 return false;
262 }, ^bool(CFMutableStringRef sql, bool *needWhere) {
263 // Select only non-D-class items
264 SecDbAppendWhereOrAnd(sql, needWhere);
265 CFStringAppendFormat(sql, NULL, CFSTR("NOT %@ IN (?,?)"), pdmn->name);
266 return true;
267 }, ^bool(sqlite3_stmt *stmt, int col) {
268 return SecDbBindObject(stmt, col++, kSecAttrAccessibleAlways, error) &&
269 SecDbBindObject(stmt, col++, kSecAttrAccessibleAlwaysThisDeviceOnly, error);
270 }, ^(SecDbItemRef item, bool *stop) {
271 CFErrorRef localError = NULL;
272
273 // Decrypt the item.
274 if (SecDbItemEnsureDecrypted(item, &localError)) {
275 // Delete SHA1 field from the item, so that it is newly recalculated before storing
276 // the item into the new table.
277 require_quiet(ok = SecDbItemSetValue(item, SecDbClassAttrWithKind(item->class, kSecDbSHA1Attr, error),
278 kCFNull, error), out);
279
280 // Replace item with the new value in the table; this will cause the item to be decoded and recoded back,
281 // incl. recalculation of item's hash.
282 ok = SecDbItemUpdate(item, item, dbt, false, error);
283 } else {
284 CFIndex status = CFErrorGetCode(localError);
285
286 // Items producing errSecDecode are silently dropped - they are not decodable and lost forever.
287 require_action_quiet(status != errSecDecode, out, ok = SecDbItemDelete(item, dbt, false, error));
288
289 // If we are still not able to decrypt the item because the class key is not released yet,
290 // remember that DB still needs phase2 migration to be run next time a connection is made. Also
291 // stop iterating next items, it would be just waste of time because the whole iteration will be run
292 // next time when this phase2 will be rerun.
293 if (status == errSecInteractionNotAllowed) {
294 *inProgress = true;
295 *stop = true;
296 } else {
297 // errSecAuthNeeded means that it is an ACL-based item which requires authentication (or at least
298 // ACM context, which we do not have). Other errors should abort the migration completely.
299 require_action_quiet(status == errSecAuthNeeded, out,
300 ok = CFErrorPropagate(CFRetainSafe(localError), error));
301 }
302 }
303
304 out:
305 CFReleaseSafe(localError);
306 *stop = *stop || !ok;
307
308 });
309 require(ok, out);
310 }
311
312 out:
313 if (query != NULL)
314 query_destroy(query, NULL);
315 return ok;
316 }
317
318 static bool SecKeychainDbUpgradeFromVersion(SecDbConnectionRef dbt, int version, bool *inProgress, CFErrorRef *error) {
319 __block bool ok = true;
320
321 // The schema we want to have is the first in the list of schemas.
322 const SecDbSchema *newSchema = kc_schemas[0];
323
324 // If DB schema is the one we want, we are done.
325 require_quiet(newSchema->version != version, out);
326
327 if (version < 6) {
328 // Pre v6 keychains need to have WAL enabled, since SecDb only does this at db creation time.
329 // NOTE: This has to be run outside of a transaction.
330 require_action_quiet(ok = (SecDbExec(dbt, CFSTR("PRAGMA auto_vacuum = FULL"), error) &&
331 SecDbExec(dbt, CFSTR("PRAGMA journal_mode = WAL"), error)),
332 out, secerror("unable to enable WAL or auto vacuum, marking DB as corrupt: %@",
333 error ? *error : NULL));
334 }
335
336 ok &= SecDbTransaction(dbt, kSecDbExclusiveTransactionType, error, ^(bool *commit) {
337 CFStringRef sql = NULL;
338
339 // Get version again once we start a transaction, someone else might change the migration state.
340 int version = 0;
341 require_quiet(ok = SecKeychainDbGetVersion(dbt, &version, error), out);
342 require_quiet(newSchema->version != version, out);
343
344 // If this is empty database, just create table according to schema and be done with it.
345 require_action_quiet(version != 0, out, ok = SecItemDbCreateSchema(dbt, newSchema, error));
346
347 int oldVersion = (version >> 16) & 0xffff;
348 version &= 0xffff;
349 require_action_quiet(version == newSchema->version || oldVersion == 0, out,
350 ok = SecDbError(SQLITE_CORRUPT, error,
351 CFSTR("Half migrated but obsolete DB found: found %d(%d) but %d is needed"),
352 version, oldVersion, newSchema->version));
353
354 // Check whether we have both old and new tables in the DB.
355 if (oldVersion == 0) {
356 // Pure old-schema migration attempt, with full blown table renames etc (a.k.a. phase1)
357 oldVersion = version;
358 version = newSchema->version;
359
360 // Find schema for old database.
361 const SecDbSchema *oldSchema = NULL;
362 for (const SecDbSchema * const *pschema = kc_schemas; *pschema; ++pschema) {
363 if ((*pschema)->version == oldVersion) {
364 oldSchema = *pschema;
365 break;
366 }
367 }
368
369 // If we are attempting to upgrade from a version for which we have no schema, fail.
370 require_action_quiet(oldSchema != NULL, out,
371 ok = SecDbError(SQLITE_CORRUPT, error, CFSTR("no schema for version: %d"), oldVersion);
372 secerror("no schema for version %d", oldVersion));
373
374 require(ok = SecKeychainDbUpgradeFromSchema(dbt, oldSchema, inProgress, error), out);
375 } else {
376 // Just go through non-D-class items in new tables and apply decode/encode on them, because
377 // they were not recoded completely during some previous old-schema migration attempt (a.k.a. phase2)
378 require(ok = SecKeychainDbUpgradeUnlockedItems(dbt, inProgress, error), out);
379 }
380
381 if (!*inProgress) {
382 // If either migration path we did reported that the migration was complete, signalize that
383 // in the version database by cleaning oldVersion (which is stored in upper halfword of the version)
384 oldVersion = 0;
385 }
386
387 // Update database version table.
388 version |= oldVersion << 16;
389 sql = CFStringCreateWithFormat(NULL, NULL, CFSTR("UPDATE %@ SET %@ = %d"),
390 tversion_class.name, tversion_class.attrs[0]->name, version);
391 require_quiet(ok = SecDbExec(dbt, sql, error), out);
392
393 out:
394 CFReleaseSafe(sql);
395 *commit = ok;
396 });
397
398 out:
399 if (!ok) {
400 secerror("unable to complete upgrade, marking DB as corrupt: %@", error ? *error : NULL);
401 SecDbCorrupt(dbt);
402 }
403
404 return ok;
405 }
406
407 /* AUDIT[securityd](done):
408 accessGroup (ok) is a caller provided, non NULL CFTypeRef.
409
410 Return true iff accessGroup is allowable according to accessGroups.
411 */
412 static bool accessGroupsAllows(CFArrayRef accessGroups,
413 CFStringRef accessGroup) {
414 /* NULL accessGroups is wildcard. */
415 if (!accessGroups)
416 return true;
417 /* Make sure we have a string. */
418 if (!isString(accessGroup))
419 return false;
420
421 /* Having the special accessGroup "*" allows access to all accessGroups. */
422 CFRange range = { 0, CFArrayGetCount(accessGroups) };
423 if (range.length &&
424 (CFArrayContainsValue(accessGroups, range, accessGroup) ||
425 CFArrayContainsValue(accessGroups, range, CFSTR("*"))))
426 return true;
427
428 return false;
429 }
430
431 bool itemInAccessGroup(CFDictionaryRef item, CFArrayRef accessGroups) {
432 return accessGroupsAllows(accessGroups,
433 CFDictionaryGetValue(item, kSecAttrAccessGroup));
434 }
435
436
437 static CF_RETURNS_RETAINED CFDataRef SecServerExportBackupableKeychain(SecDbConnectionRef dbt,
438 keybag_handle_t src_keybag, keybag_handle_t dest_keybag, CFErrorRef *error) {
439 CFDataRef data_out = NULL;
440 /* Export everything except the items for which SecItemIsSystemBound()
441 returns true. */
442 CFDictionaryRef keychain = SecServerExportKeychainPlist(dbt,
443 src_keybag, dest_keybag, kSecBackupableItemFilter,
444 error);
445 if (keychain) {
446 data_out = CFPropertyListCreateData(kCFAllocatorDefault, keychain,
447 kCFPropertyListBinaryFormat_v1_0,
448 0, error);
449 CFRelease(keychain);
450 }
451
452 return data_out;
453 }
454
455 static bool SecServerImportBackupableKeychain(SecDbConnectionRef dbt,
456 keybag_handle_t src_keybag,
457 keybag_handle_t dest_keybag, CFDataRef data, CFErrorRef *error) {
458 return kc_transaction(dbt, error, ^{
459 bool ok = false;
460 CFDictionaryRef keychain;
461 keychain = CFPropertyListCreateWithData(kCFAllocatorDefault, data,
462 kCFPropertyListImmutable, NULL,
463 error);
464 if (keychain) {
465 if (isDictionary(keychain)) {
466 ok = SecServerImportKeychainInPlist(dbt, src_keybag,
467 dest_keybag, keychain,
468 kSecBackupableItemFilter,
469 error);
470 } else {
471 ok = SecError(errSecParam, error, CFSTR("import: keychain is not a dictionary"));
472 }
473 CFRelease(keychain);
474 }
475 return ok;
476 });
477 }
478
479 static CF_RETURNS_RETAINED CFDataRef SecServerKeychainBackup(SecDbConnectionRef dbt, CFDataRef keybag,
480 CFDataRef password, CFErrorRef *error) {
481 CFDataRef backup = NULL;
482 keybag_handle_t backup_keybag;
483 if (ks_open_keybag(keybag, password, &backup_keybag, error)) {
484 /* Export from system keybag to backup keybag. */
485 backup = SecServerExportBackupableKeychain(dbt, KEYBAG_DEVICE, backup_keybag, error);
486 if (!ks_close_keybag(backup_keybag, error)) {
487 CFReleaseNull(backup);
488 }
489 }
490 return backup;
491 }
492
493 static bool SecServerKeychainRestore(SecDbConnectionRef dbt, CFDataRef backup,
494 CFDataRef keybag, CFDataRef password, CFErrorRef *error) {
495 keybag_handle_t backup_keybag;
496 if (!ks_open_keybag(keybag, password, &backup_keybag, error))
497 return false;
498
499 /* Import from backup keybag to system keybag. */
500 bool ok = SecServerImportBackupableKeychain(dbt, backup_keybag, KEYBAG_DEVICE,
501 backup, error);
502 ok &= ks_close_keybag(backup_keybag, error);
503
504 return ok;
505 }
506
507
508 // MARK - External SPI support code.
509
510 CFStringRef __SecKeychainCopyPath(void) {
511 CFStringRef kcRelPath = NULL;
512 if (use_hwaes()) {
513 kcRelPath = CFSTR("keychain-2.db");
514 } else {
515 kcRelPath = CFSTR("keychain-2-debug.db");
516 }
517
518 CFStringRef kcPath = NULL;
519 CFURLRef kcURL = SecCopyURLForFileInKeychainDirectory(kcRelPath);
520 if (kcURL) {
521 kcPath = CFURLCopyFileSystemPath(kcURL, kCFURLPOSIXPathStyle);
522 CFRelease(kcURL);
523 }
524 return kcPath;
525 }
526
527 // MARK; -
528 // MARK: kc_dbhandle init and reset
529
530 SecDbRef SecKeychainDbCreate(CFStringRef path) {
531 return SecDbCreate(path, ^bool (SecDbConnectionRef dbconn, bool didCreate, bool *callMeAgainForNextConnection, CFErrorRef *error) {
532 // Upgrade from version 0 means create the schema in empty db.
533 int version = 0;
534 bool ok = true;
535 if (!didCreate)
536 ok = SecKeychainDbGetVersion(dbconn, &version, error);
537
538 ok = ok && SecKeychainDbUpgradeFromVersion(dbconn, version, callMeAgainForNextConnection, error);
539 if (!ok)
540 secerror("Upgrade %sfailed: %@", didCreate ? "from v0 " : "", error ? *error : NULL);
541
542 return ok;
543 });
544 }
545
546 static SecDbRef _kc_dbhandle = NULL;
547
548 static void kc_dbhandle_init(void) {
549 SecDbRef oldHandle = _kc_dbhandle;
550 _kc_dbhandle = NULL;
551 CFStringRef dbPath = __SecKeychainCopyPath();
552 if (dbPath) {
553 _kc_dbhandle = SecKeychainDbCreate(dbPath);
554 CFRelease(dbPath);
555 } else {
556 secerror("no keychain path available");
557 }
558 if (oldHandle) {
559 secerror("replaced %@ with %@", oldHandle, _kc_dbhandle);
560 CFRelease(oldHandle);
561 }
562 }
563
564 // A callback for the sqlite3_log() interface.
565 static void sqlite3Log(void *pArg, int iErrCode, const char *zMsg){
566 secinfo("sqlite3", "(%d) %s", iErrCode, zMsg);
567 }
568
569 static void setup_sqlite3_defaults_settings() {
570 int rx = sqlite3_config(SQLITE_CONFIG_LOG, sqlite3Log, NULL);
571 if (SQLITE_OK != rx) {
572 secwarning("Could not set up sqlite global error logging to syslog: %d", rx);
573 }
574 }
575
576 static dispatch_once_t _kc_dbhandle_once;
577
578 static SecDbRef kc_dbhandle(void) {
579 dispatch_once(&_kc_dbhandle_once, ^{
580 setup_sqlite3_defaults_settings();
581 kc_dbhandle_init();
582 });
583 return _kc_dbhandle;
584 }
585
586 /* For whitebox testing only */
587 void kc_dbhandle_reset(void);
588 void kc_dbhandle_reset(void)
589 {
590 __block bool done = false;
591 dispatch_once(&_kc_dbhandle_once, ^{
592 kc_dbhandle_init();
593 done = true;
594 });
595 // TODO: Not thread safe at all! - FOR DEBUGGING ONLY
596 if (!done)
597 kc_dbhandle_init();
598 }
599
600 static SecDbConnectionRef kc_aquire_dbt(bool writeAndRead, CFErrorRef *error) {
601 SecDbRef db = kc_dbhandle();
602 if (db == NULL) {
603 SecError(errSecDataNotAvailable, error, CFSTR("failed to get a db handle"));
604 return NULL;
605 }
606 return SecDbConnectionAquire(db, !writeAndRead, error);
607 }
608
609 /* Return a per thread dbt handle for the keychain. If create is true create
610 the database if it does not yet exist. If it is false, just return an
611 error if it fails to auto-create. */
612 static bool kc_with_dbt(bool writeAndRead, CFErrorRef *error, bool (^perform)(SecDbConnectionRef dbt))
613 {
614 // Make sure we initialize our engines before writing to the keychain
615 if (writeAndRead)
616 SecItemDataSourceFactoryGetDefault();
617
618 bool ok = false;
619 SecDbConnectionRef dbt = kc_aquire_dbt(writeAndRead, error);
620 if (dbt) {
621 ok = perform(dbt);
622 SecDbConnectionRelease(dbt);
623 }
624 return ok;
625 }
626
627 static bool
628 items_matching_issuer_parent(SecDbConnectionRef dbt, CFArrayRef accessGroups,
629 CFDataRef issuer, CFArrayRef issuers, int recurse)
630 {
631 Query *q;
632 CFArrayRef results = NULL;
633 CFIndex i, count;
634 bool found = false;
635
636 if (CFArrayContainsValue(issuers, CFRangeMake(0, CFArrayGetCount(issuers)), issuer))
637 return true;
638
639 const void *keys[] = { kSecClass, kSecReturnRef, kSecAttrSubject };
640 const void *vals[] = { kSecClassCertificate, kCFBooleanTrue, issuer };
641 CFDictionaryRef query = CFDictionaryCreate(kCFAllocatorDefault, keys, vals, array_size(keys), NULL, NULL);
642
643 if (!query)
644 return false;
645
646 CFErrorRef localError = NULL;
647 q = query_create_with_limit(query, kSecMatchUnlimited, &localError);
648 CFRelease(query);
649 if (q) {
650 s3dl_copy_matching(dbt, q, (CFTypeRef*)&results, accessGroups, &localError);
651 query_destroy(q, &localError);
652 }
653 if (localError) {
654 secerror("items matching issuer parent: %@", localError);
655 CFReleaseNull(localError);
656 return false;
657 }
658
659 count = CFArrayGetCount(results);
660 for (i = 0; (i < count) && !found; i++) {
661 CFDictionaryRef cert_dict = (CFDictionaryRef)CFArrayGetValueAtIndex(results, i);
662 CFDataRef cert_issuer = CFDictionaryGetValue(cert_dict, kSecAttrIssuer);
663 if (CFEqual(cert_issuer, issuer))
664 continue;
665 if (recurse-- > 0)
666 found = items_matching_issuer_parent(dbt, accessGroups, cert_issuer, issuers, recurse);
667 }
668 CFReleaseSafe(results);
669
670 return found;
671 }
672
673 bool match_item(SecDbConnectionRef dbt, Query *q, CFArrayRef accessGroups, CFDictionaryRef item)
674 {
675 if (q->q_match_issuer) {
676 CFDataRef issuer = CFDictionaryGetValue(item, kSecAttrIssuer);
677 if (!items_matching_issuer_parent(dbt, accessGroups, issuer, q->q_match_issuer, 10 /*max depth*/))
678 return false;
679 }
680
681 /* Add future match checks here. */
682
683 return true;
684 }
685
686 /****************************************************************************
687 **************** Beginning of Externally Callable Interface ****************
688 ****************************************************************************/
689
690 #if 0
691 // TODO Use as a safety wrapper
692 static bool SecErrorWith(CFErrorRef *in_error, bool (^perform)(CFErrorRef *error)) {
693 CFErrorRef error = in_error ? *in_error : NULL;
694 bool ok;
695 if ((ok = perform(&error))) {
696 assert(error == NULL);
697 if (error)
698 secerror("error + success: %@", error);
699 } else {
700 assert(error);
701 OSStatus status = SecErrorGetOSStatus(error);
702 if (status != errSecItemNotFound) // Occurs in normal operation, so exclude
703 secerror("error:[%" PRIdOSStatus "] %@", status, error);
704 if (in_error) {
705 *in_error = error;
706 } else {
707 CFReleaseNull(error);
708 }
709 }
710 return ok;
711 }
712 #endif
713
714 /* AUDIT[securityd](done):
715 query (ok) is a caller provided dictionary, only its cf type has been checked.
716 */
717 static bool
718 SecItemServerCopyMatching(CFDictionaryRef query, CFTypeRef *result,
719 CFArrayRef accessGroups, CFErrorRef *error)
720 {
721 CFIndex ag_count;
722 if (!accessGroups || 0 == (ag_count = CFArrayGetCount(accessGroups))) {
723 return SecError(errSecMissingEntitlement, error,
724 CFSTR("client has neither application-identifier nor keychain-access-groups entitlements"));
725 }
726
727 if (CFArrayContainsValue(accessGroups, CFRangeMake(0, ag_count), CFSTR("*"))) {
728 /* Having the special accessGroup "*" allows access to all accessGroups. */
729 accessGroups = NULL;
730 }
731
732 bool ok = false;
733 Query *q = query_create_with_limit(query, 1, error);
734 if (q) {
735 CFStringRef agrp = CFDictionaryGetValue(q->q_item, kSecAttrAccessGroup);
736 if (agrp && accessGroupsAllows(accessGroups, agrp)) {
737 // TODO: Return an error if agrp is not NULL and accessGroupsAllows() fails above.
738 const void *val = agrp;
739 accessGroups = CFArrayCreate(0, &val, 1, &kCFTypeArrayCallBacks);
740 } else {
741 CFRetainSafe(accessGroups);
742 }
743
744 query_set_caller_access_groups(q, accessGroups);
745
746 /* Sanity check the query. */
747 if (q->q_use_item_list) {
748 ok = SecError(errSecUseItemListUnsupported, error, CFSTR("use item list unsupported"));
749 #if defined(MULTIPLE_KEYCHAINS)
750 } else if (q->q_use_keychain) {
751 ok = SecError(errSecUseKeychainUnsupported, error, CFSTR("use keychain list unsupported"));
752 #endif
753 } else if (q->q_match_issuer && ((q->q_class != &cert_class) &&
754 (q->q_class != &identity_class))) {
755 ok = SecError(errSecUnsupportedOperation, error, CFSTR("unsupported match attribute"));
756 } else if (q->q_return_type != 0 && result == NULL) {
757 ok = SecError(errSecReturnMissingPointer, error, CFSTR("missing pointer"));
758 } else if (!q->q_error) {
759 ok = kc_with_dbt(false, error, ^(SecDbConnectionRef dbt) {
760 return s3dl_copy_matching(dbt, q, result, accessGroups, error);
761 });
762 }
763
764 CFReleaseSafe(accessGroups);
765 if (!query_destroy(q, error))
766 ok = false;
767 }
768
769 return ok;
770 }
771
772 bool
773 _SecItemCopyMatching(CFDictionaryRef query, CFArrayRef accessGroups, CFTypeRef *result, CFErrorRef *error) {
774 return SecItemServerCopyMatching(query, result, accessGroups, error);
775 }
776
777 /* AUDIT[securityd](done):
778 attributes (ok) is a caller provided dictionary, only its cf type has
779 been checked.
780 */
781 bool
782 _SecItemAdd(CFDictionaryRef attributes, CFArrayRef accessGroups,
783 CFTypeRef *result, CFErrorRef *error)
784 {
785 bool ok = true;
786 CFIndex ag_count;
787 if (!accessGroups || 0 == (ag_count = CFArrayGetCount(accessGroups))) {
788 return SecError(errSecMissingEntitlement, error,
789 CFSTR("client has neither application-identifier nor keychain-access-groups entitlements"));
790 }
791
792 Query *q = query_create_with_limit(attributes, 0, error);
793 if (q) {
794 /* Access group sanity checking. */
795 CFStringRef agrp = (CFStringRef)CFDictionaryGetValue(attributes,
796 kSecAttrAccessGroup);
797
798 CFArrayRef ag = accessGroups;
799 /* Having the special accessGroup "*" allows access to all accessGroups. */
800 if (CFArrayContainsValue(accessGroups, CFRangeMake(0, ag_count), CFSTR("*")))
801 accessGroups = NULL;
802
803 if (agrp) {
804 /* The user specified an explicit access group, validate it. */
805 if (!accessGroupsAllows(accessGroups, agrp))
806 ok = SecError(errSecNoAccessForItem, error, CFSTR("NoAccessForItem"));
807 } else {
808 agrp = (CFStringRef)CFArrayGetValueAtIndex(ag, 0);
809
810 /* We are using an implicit access group, add it as if the user
811 specified it as an attribute. */
812 query_add_attribute(kSecAttrAccessGroup, agrp, q);
813 }
814
815 if (ok) {
816 query_ensure_access_control(q, agrp);
817
818 if (q->q_row_id)
819 ok = SecError(errSecValuePersistentRefUnsupported, error, CFSTR("q_row_id")); // TODO: better error string
820 #if defined(MULTIPLE_KEYCHAINS)
821 else if (q->q_use_keychain_list)
822 ok = SecError(errSecUseKeychainListUnsupported, error, CFSTR("q_use_keychain_list")); // TODO: better error string;
823 #endif
824 else if (!q->q_error) {
825 ok = kc_with_dbt(true, error, ^(SecDbConnectionRef dbt){
826 return kc_transaction(dbt, error, ^{
827 query_pre_add(q, true);
828 return s3dl_query_add(dbt, q, result, error);
829 });
830 });
831 }
832 }
833 ok = query_notify_and_destroy(q, ok, error);
834 } else {
835 ok = false;
836 }
837 return ok;
838 }
839
840 /* AUDIT[securityd](done):
841 query (ok) and attributesToUpdate (ok) are a caller provided dictionaries,
842 only their cf types have been checked.
843 */
844 bool
845 _SecItemUpdate(CFDictionaryRef query, CFDictionaryRef attributesToUpdate,
846 CFArrayRef accessGroups, CFErrorRef *error)
847 {
848 CFIndex ag_count;
849 if (!accessGroups || 0 == (ag_count = CFArrayGetCount(accessGroups))) {
850 return SecError(errSecMissingEntitlement, error,
851 CFSTR("client has neither application-identifier nor keychain-access-groups entitlements"));
852 }
853
854 if (CFArrayContainsValue(accessGroups, CFRangeMake(0, ag_count), CFSTR("*"))) {
855 /* Having the special accessGroup "*" allows access to all accessGroups. */
856 accessGroups = NULL;
857 }
858
859 bool ok = true;
860 Query *q = query_create_with_limit(query, kSecMatchUnlimited, error);
861 if (!q) {
862 ok = false;
863 }
864 if (ok) {
865 /* Sanity check the query. */
866 query_set_caller_access_groups(q, accessGroups);
867 if (q->q_use_item_list) {
868 ok = SecError(errSecUseItemListUnsupported, error, CFSTR("use item list not supported"));
869 } else if (q->q_return_type & kSecReturnDataMask) {
870 /* Update doesn't return anything so don't ask for it. */
871 ok = SecError(errSecReturnDataUnsupported, error, CFSTR("return data not supported by update"));
872 } else if (q->q_return_type & kSecReturnAttributesMask) {
873 ok = SecError(errSecReturnAttributesUnsupported, error, CFSTR("return attributes not supported by update"));
874 } else if (q->q_return_type & kSecReturnRefMask) {
875 ok = SecError(errSecReturnRefUnsupported, error, CFSTR("return ref not supported by update"));
876 } else if (q->q_return_type & kSecReturnPersistentRefMask) {
877 ok = SecError(errSecReturnPersistentRefUnsupported, error, CFSTR("return persistent ref not supported by update"));
878 } else {
879 /* Access group sanity checking. */
880 CFStringRef agrp = (CFStringRef)CFDictionaryGetValue(attributesToUpdate,
881 kSecAttrAccessGroup);
882 if (agrp) {
883 /* The user is attempting to modify the access group column,
884 validate it to make sure the new value is allowable. */
885 if (!accessGroupsAllows(accessGroups, agrp)) {
886 ok = SecError(errSecNoAccessForItem, error, CFSTR("accessGroup %@ not in %@"), agrp, accessGroups);
887 }
888 }
889 }
890 }
891 if (ok) {
892 ok = kc_with_dbt(true, error, ^(SecDbConnectionRef dbt) {
893 return s3dl_query_update(dbt, q, attributesToUpdate, accessGroups, error);
894 });
895 }
896 if (q) {
897 ok = query_notify_and_destroy(q, ok, error);
898 }
899 return ok;
900 }
901
902
903 /* AUDIT[securityd](done):
904 query (ok) is a caller provided dictionary, only its cf type has been checked.
905 */
906 bool
907 _SecItemDelete(CFDictionaryRef query, CFArrayRef accessGroups, CFErrorRef *error)
908 {
909 CFIndex ag_count;
910 if (!accessGroups || 0 == (ag_count = CFArrayGetCount(accessGroups))) {
911 return SecError(errSecMissingEntitlement, error,
912 CFSTR("client has neither application-identifier nor keychain-access-groups entitlements"));
913 }
914
915 if (CFArrayContainsValue(accessGroups, CFRangeMake(0, ag_count), CFSTR("*"))) {
916 /* Having the special accessGroup "*" allows access to all accessGroups. */
917 accessGroups = NULL;
918 }
919
920 Query *q = query_create_with_limit(query, kSecMatchUnlimited, error);
921 bool ok;
922 if (q) {
923 query_set_caller_access_groups(q, accessGroups);
924 /* Sanity check the query. */
925 if (q->q_limit != kSecMatchUnlimited)
926 ok = SecError(errSecMatchLimitUnsupported, error, CFSTR("match limit not supported by delete"));
927 else if (query_match_count(q) != 0)
928 ok = SecError(errSecItemMatchUnsupported, error, CFSTR("match not supported by delete"));
929 else if (q->q_ref)
930 ok = SecError(errSecValueRefUnsupported, error, CFSTR("value ref not supported by delete"));
931 else if (q->q_row_id && query_attr_count(q))
932 ok = SecError(errSecItemIllegalQuery, error, CFSTR("rowid and other attributes are mutually exclusive"));
933 else {
934 ok = kc_with_dbt(true, error, ^(SecDbConnectionRef dbt) {
935 return s3dl_query_delete(dbt, q, accessGroups, error);
936 });
937 }
938 ok = query_notify_and_destroy(q, ok, error);
939 } else {
940 ok = false;
941 }
942 return ok;
943 }
944
945
946 /* AUDIT[securityd](done):
947 No caller provided inputs.
948 */
949 static bool
950 SecItemServerDeleteAll(CFErrorRef *error) {
951 return kc_with_dbt(true, error, ^bool (SecDbConnectionRef dbt) {
952 return (kc_transaction(dbt, error, ^bool {
953 return (SecDbExec(dbt, CFSTR("DELETE from genp;"), error) &&
954 SecDbExec(dbt, CFSTR("DELETE from inet;"), error) &&
955 SecDbExec(dbt, CFSTR("DELETE from cert;"), error) &&
956 SecDbExec(dbt, CFSTR("DELETE from keys;"), error));
957 }) && SecDbExec(dbt, CFSTR("VACUUM;"), error));
958 });
959 }
960
961 bool
962 _SecItemDeleteAll(CFErrorRef *error) {
963 return SecItemServerDeleteAll(error);
964 }
965
966
967 // MARK: -
968 // MARK: Shared web credentials
969
970 /* constants */
971 #define SEC_CONST_DECL(k,v) const CFStringRef k = CFSTR(v);
972
973 SEC_CONST_DECL (kSecSafariAccessGroup, "com.apple.cfnetwork");
974 SEC_CONST_DECL (kSecSafariDefaultComment, "default");
975 SEC_CONST_DECL (kSecSafariPasswordsNotSaved, "Passwords not saved");
976 SEC_CONST_DECL (kSecSharedCredentialUrlScheme, "https://");
977 SEC_CONST_DECL (kSecSharedWebCredentialsService, "webcredentials");
978
979 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
980 static dispatch_once_t sSecSWCInitializeOnce = 0;
981 static void * sSecSWCLibrary = NULL;
982 static SWCCheckService_f sSWCCheckService_f = NULL;
983 static SWCSetServiceFlags_f sSWCSetServiceFlags_f = NULL;
984
985 static OSStatus _SecSWCEnsuredInitialized(void);
986
987 static OSStatus _SecSWCEnsuredInitialized(void)
988 {
989 __block OSStatus status = errSecNotAvailable;
990
991 dispatch_once(&sSecSWCInitializeOnce, ^{
992 sSecSWCLibrary = dlopen("/System/Library/PrivateFrameworks/SharedWebCredentials.framework/SharedWebCredentials", RTLD_LAZY | RTLD_LOCAL);
993 assert(sSecSWCLibrary);
994 if (sSecSWCLibrary) {
995 sSWCCheckService_f = (SWCCheckService_f)(uintptr_t) dlsym(sSecSWCLibrary, "SWCCheckService");
996 sSWCSetServiceFlags_f = (SWCSetServiceFlags_f)(uintptr_t) dlsym(sSecSWCLibrary, "SWCSetServiceFlags");
997 }
998 });
999
1000 if (sSWCCheckService_f && sSWCSetServiceFlags_f) {
1001 status = noErr;
1002 }
1003 return status;
1004 }
1005 #endif
1006
1007 #if !TARGET_IPHONE_SIMULATOR
1008 static SWCFlags
1009 _SecAppDomainApprovalStatus(CFStringRef appID, CFStringRef fqdn, CFErrorRef *error)
1010 {
1011 __block SWCFlags flags = kSWCFlags_None;
1012
1013 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1014 OSStatus status = _SecSWCEnsuredInitialized();
1015 if (status) {
1016 SecError(status, error, CFSTR("SWC initialize failed"));
1017 return flags;
1018 }
1019 CFRetainSafe(appID);
1020 CFRetainSafe(fqdn);
1021 dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
1022 dispatch_retain(semaphore);
1023 if (0 == sSWCCheckService_f(kSecSharedWebCredentialsService, appID, fqdn,
1024 ^void (OSStatus inStatus, SWCFlags inFlags, CFDictionaryRef inDetails) {
1025 if (!inStatus) { flags = inFlags; }
1026 CFReleaseSafe(appID);
1027 CFReleaseSafe(fqdn);
1028 dispatch_semaphore_signal(semaphore);
1029 dispatch_release(semaphore);
1030 //secerror("SWCCheckService: inStatus=%d, flags=%0X", inStatus, flags);
1031 }))
1032 {
1033 // wait for the block to complete, as we need its answer
1034 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
1035 }
1036 else // didn't queue the block
1037 {
1038 CFReleaseSafe(appID);
1039 CFReleaseSafe(fqdn);
1040 dispatch_release(semaphore);
1041 }
1042 dispatch_release(semaphore);
1043 #else
1044 flags |= (kSWCFlag_SiteApproved);
1045 #endif
1046
1047 if (!error) { return flags; }
1048 *error = NULL;
1049
1050 // check website approval status
1051 if (!(flags & kSWCFlag_SiteApproved)) {
1052 if (flags & kSWCFlag_Pending) {
1053 SecError(errSecAuthFailed, error, CFSTR("Approval is pending for \"%@\", try later"), fqdn);
1054 } else {
1055 SecError(errSecAuthFailed, error, CFSTR("\"%@\" failed to approve \"%@\""), fqdn, appID);
1056 }
1057 return flags;
1058 }
1059
1060 // check user approval status
1061 if (flags & kSWCFlag_UserDenied) {
1062 SecError(errSecAuthFailed, error, CFSTR("User denied access to \"%@\" by \"%@\""), fqdn, appID);
1063 }
1064 return flags;
1065 }
1066 #endif
1067
1068 #if !TARGET_IPHONE_SIMULATOR
1069 static bool
1070 _SecEntitlementContainsDomainForService(CFArrayRef domains, CFStringRef domain, CFStringRef service)
1071 {
1072 bool result = false;
1073 CFIndex idx, count = (domains) ? CFArrayGetCount(domains) : (CFIndex) 0;
1074 if (!count || !domain || !service) {
1075 return result;
1076 }
1077 for (idx=0; idx < count; idx++) {
1078 CFStringRef str = (CFStringRef) CFArrayGetValueAtIndex(domains, idx);
1079 if (str && CFStringHasPrefix(str, kSecSharedWebCredentialsService)) {
1080 CFIndex prefix_len = CFStringGetLength(kSecSharedWebCredentialsService)+1;
1081 CFIndex substr_len = CFStringGetLength(str) - prefix_len;
1082 CFRange range = { prefix_len, substr_len };
1083 CFStringRef substr = CFStringCreateWithSubstring(kCFAllocatorDefault, str, range);
1084 if (substr && CFEqual(substr, domain)) {
1085 result = true;
1086 }
1087 CFReleaseSafe(substr);
1088 if (result) {
1089 break;
1090 }
1091 }
1092 }
1093 return result;
1094 }
1095 #endif
1096
1097 static bool
1098 _SecAddNegativeWebCredential(CFStringRef fqdn, CFStringRef appID, bool forSafari)
1099 {
1100 bool result = false;
1101 if (!fqdn) { return result; }
1102
1103 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1104 OSStatus status = _SecSWCEnsuredInitialized();
1105 if (status) { return false; }
1106
1107 // update our database
1108 CFRetainSafe(appID);
1109 CFRetainSafe(fqdn);
1110 if (0 == sSWCSetServiceFlags_f(kSecSharedWebCredentialsService,
1111 appID, fqdn, kSWCFlag_ExternalMask, kSWCFlag_UserDenied,
1112 ^void(OSStatus inStatus, SWCFlags inNewFlags){
1113 CFReleaseSafe(appID);
1114 CFReleaseSafe(fqdn);
1115 }))
1116 {
1117 result = true;
1118 }
1119 else // didn't queue the block
1120 {
1121 CFReleaseSafe(appID);
1122 CFReleaseSafe(fqdn);
1123 }
1124 #endif
1125 if (!forSafari) { return result; }
1126
1127 // below this point: create a negative Safari web credential item
1128
1129 CFMutableDictionaryRef attrs = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1130 if (!attrs) { return result; }
1131
1132 CFErrorRef error = NULL;
1133 CFStringRef accessGroup = CFSTR("*");
1134 CFArrayRef accessGroups = CFArrayCreate(kCFAllocatorDefault, (const void **)&accessGroup, 1, &kCFTypeArrayCallBacks);
1135
1136 CFDictionaryAddValue(attrs, kSecClass, kSecClassInternetPassword);
1137 CFDictionaryAddValue(attrs, kSecAttrAccessGroup, kSecSafariAccessGroup);
1138 CFDictionaryAddValue(attrs, kSecAttrAuthenticationType, kSecAttrAuthenticationTypeHTMLForm);
1139 CFDictionaryAddValue(attrs, kSecAttrProtocol, kSecAttrProtocolHTTPS);
1140 CFDictionaryAddValue(attrs, kSecAttrServer, fqdn);
1141 CFDictionaryAddValue(attrs, kSecAttrSynchronizable, kCFBooleanTrue);
1142
1143 (void)_SecItemDelete(attrs, accessGroups, &error);
1144 CFReleaseNull(error);
1145
1146 CFDictionaryAddValue(attrs, kSecAttrAccount, kSecSafariPasswordsNotSaved);
1147 CFDictionaryAddValue(attrs, kSecAttrComment, kSecSafariDefaultComment);
1148
1149 CFStringRef label = CFStringCreateWithFormat(kCFAllocatorDefault,
1150 NULL, CFSTR("%@ (%@)"), fqdn, kSecSafariPasswordsNotSaved);
1151 if (label) {
1152 CFDictionaryAddValue(attrs, kSecAttrLabel, label);
1153 CFReleaseSafe(label);
1154 }
1155
1156 UInt8 space = ' ';
1157 CFDataRef data = CFDataCreate(kCFAllocatorDefault, &space, 1);
1158 if (data) {
1159 CFDictionarySetValue(attrs, kSecValueData, data);
1160 CFReleaseSafe(data);
1161 }
1162
1163 CFTypeRef addResult = NULL;
1164 result = _SecItemAdd(attrs, accessGroups, &addResult, &error);
1165
1166 CFReleaseSafe(addResult);
1167 CFReleaseSafe(error);
1168 CFReleaseSafe(attrs);
1169 CFReleaseSafe(accessGroups);
1170
1171 return result;
1172 }
1173
1174 /* Specialized version of SecItemAdd for shared web credentials */
1175 bool
1176 _SecAddSharedWebCredential(CFDictionaryRef attributes,
1177 const audit_token_t *clientAuditToken,
1178 CFStringRef appID,
1179 CFArrayRef domains,
1180 CFTypeRef *result,
1181 CFErrorRef *error) {
1182
1183 CFStringRef fqdn = CFDictionaryGetValue(attributes, kSecAttrServer);
1184 CFStringRef account = CFDictionaryGetValue(attributes, kSecAttrAccount);
1185 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1186 CFStringRef password = CFDictionaryGetValue(attributes, kSecSharedPassword);
1187 #else
1188 CFStringRef password = CFDictionaryGetValue(attributes, CFSTR("spwd"));
1189 #endif
1190 CFStringRef accessGroup = CFSTR("*");
1191 CFArrayRef accessGroups = NULL;
1192 CFMutableDictionaryRef query = NULL, attrs = NULL;
1193 SInt32 port = -1;
1194 bool ok = false, update = false;
1195 //bool approved = false;
1196
1197 // check autofill enabled status
1198 if (!swca_autofill_enabled(clientAuditToken)) {
1199 SecError(errSecBadReq, error, CFSTR("Autofill is not enabled in Safari settings"));
1200 goto cleanup;
1201 }
1202
1203 // parse fqdn with CFURL here, since it could be specified as domain:port
1204 if (fqdn) {
1205 CFRetainSafe(fqdn);
1206 CFStringRef urlStr = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@%@"), kSecSharedCredentialUrlScheme, fqdn);
1207 if (urlStr) {
1208 CFURLRef url = CFURLCreateWithString(kCFAllocatorDefault, urlStr, nil);
1209 if (url) {
1210 CFStringRef hostname = CFURLCopyHostName(url);
1211 if (hostname) {
1212 CFReleaseSafe(fqdn);
1213 fqdn = hostname;
1214 port = CFURLGetPortNumber(url);
1215 }
1216 CFReleaseSafe(url);
1217 }
1218 CFReleaseSafe(urlStr);
1219 }
1220 }
1221
1222 if (!account) {
1223 SecError(errSecParam, error, CFSTR("No account provided"));
1224 goto cleanup;
1225 }
1226 if (!fqdn) {
1227 SecError(errSecParam, error, CFSTR("No domain provided"));
1228 goto cleanup;
1229 }
1230
1231 #if TARGET_IPHONE_SIMULATOR
1232 secerror("app/site association entitlements not checked in Simulator");
1233 #else
1234 OSStatus status = errSecMissingEntitlement;
1235 // validate that fqdn is part of caller's shared credential domains entitlement
1236 if (!appID) {
1237 SecError(status, error, CFSTR("Missing application-identifier entitlement"));
1238 goto cleanup;
1239 }
1240 if (_SecEntitlementContainsDomainForService(domains, fqdn, kSecSharedWebCredentialsService)) {
1241 status = errSecSuccess;
1242 }
1243 if (errSecSuccess != status) {
1244 CFStringRef msg = CFStringCreateWithFormat(kCFAllocatorDefault, NULL,
1245 CFSTR("%@ not found in %@ entitlement"), fqdn, kSecEntitlementAssociatedDomains);
1246 if (!msg) {
1247 msg = CFRetain(CFSTR("Requested domain not found in entitlement"));
1248 }
1249 SecError(status, error, CFSTR("%@"), msg);
1250 CFReleaseSafe(msg);
1251 goto cleanup;
1252 }
1253 #endif
1254
1255 #if TARGET_IPHONE_SIMULATOR
1256 secerror("Ignoring app/site approval state in the Simulator.");
1257 #else
1258 // get approval status for this app/domain pair
1259 SWCFlags flags = _SecAppDomainApprovalStatus(appID, fqdn, error);
1260 //approved = ((flags & kSWCFlag_SiteApproved) && (flags & kSWCFlag_UserApproved));
1261 if (!(flags & kSWCFlag_SiteApproved)) {
1262 goto cleanup;
1263 }
1264 #endif
1265
1266 // give ourselves access to see matching items for kSecSafariAccessGroup
1267 accessGroups = CFArrayCreate(kCFAllocatorDefault, (const void **)&accessGroup, 1, &kCFTypeArrayCallBacks);
1268
1269 // create lookup query
1270 query = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1271 if (!query) {
1272 SecError(errSecAllocate, error, CFSTR("Unable to create query dictionary"));
1273 goto cleanup;
1274 }
1275 CFDictionaryAddValue(query, kSecClass, kSecClassInternetPassword);
1276 CFDictionaryAddValue(query, kSecAttrAccessGroup, kSecSafariAccessGroup);
1277 CFDictionaryAddValue(query, kSecAttrAuthenticationType, kSecAttrAuthenticationTypeHTMLForm);
1278 CFDictionaryAddValue(query, kSecAttrServer, fqdn);
1279 CFDictionaryAddValue(query, kSecAttrSynchronizable, kCFBooleanTrue);
1280
1281 // check for presence of Safari's negative entry ('passwords not saved')
1282 CFDictionarySetValue(query, kSecAttrAccount, kSecSafariPasswordsNotSaved);
1283 ok = _SecItemCopyMatching(query, accessGroups, result, error);
1284 CFReleaseNull(*result);
1285 CFReleaseNull(*error);
1286 if (ok) {
1287 SecError(errSecDuplicateItem, error, CFSTR("Item already exists for this server"));
1288 goto cleanup;
1289 }
1290
1291 // now use the provided account (and optional port number, if one was present)
1292 CFDictionarySetValue(query, kSecAttrAccount, account);
1293 if (port < -1 || port > 0) {
1294 SInt16 portValueShort = (port & 0xFFFF);
1295 CFNumberRef portNumber = CFNumberCreate(NULL, kCFNumberSInt16Type, &portValueShort);
1296 CFDictionaryAddValue(query, kSecAttrPort, portNumber);
1297 CFReleaseSafe(portNumber);
1298 }
1299
1300 // look up existing password
1301 if (_SecItemCopyMatching(query, accessGroups, result, error)) {
1302 // found it, so this becomes either an "update password" or "delete password" operation
1303 CFReleaseNull(*result);
1304 CFReleaseNull(*error);
1305 update = (password != NULL);
1306 if (update) {
1307 attrs = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1308 CFDataRef credential = CFStringCreateExternalRepresentation(kCFAllocatorDefault, password, kCFStringEncodingUTF8, 0);
1309 CFDictionaryAddValue(attrs, kSecValueData, credential);
1310 CFReleaseSafe(credential);
1311 CFDictionaryAddValue(attrs, kSecAttrComment, kSecSafariDefaultComment);
1312
1313 // confirm the update
1314 // (per rdar://16676310 we always prompt, even if there was prior user approval)
1315 ok = /*approved ||*/ swca_confirm_operation(swca_update_request_id, clientAuditToken, query, error,
1316 ^void (CFStringRef fqdn) { _SecAddNegativeWebCredential(fqdn, appID, false); });
1317 if (ok) {
1318 ok = _SecItemUpdate(query, attrs, accessGroups, error);
1319 }
1320 }
1321 else {
1322 // confirm the delete
1323 // (per rdar://16676288 we always prompt, even if there was prior user approval)
1324 ok = /*approved ||*/ swca_confirm_operation(swca_delete_request_id, clientAuditToken, query, error,
1325 ^void (CFStringRef fqdn) { _SecAddNegativeWebCredential(fqdn, appID, false); });
1326 if (ok) {
1327 ok = _SecItemDelete(query, accessGroups, error);
1328 }
1329 }
1330 if (ok) {
1331 CFReleaseNull(*error);
1332 }
1333 goto cleanup;
1334 }
1335 CFReleaseNull(*result);
1336 CFReleaseNull(*error);
1337
1338 // password does not exist, so prepare to add it
1339 if (!password) {
1340 // a NULL password value removes the existing credential. Since we didn't find it, this is a no-op.
1341 ok = true;
1342 goto cleanup;
1343 }
1344 else {
1345 CFStringRef label = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@ (%@)"), fqdn, account);
1346 if (label) {
1347 CFDictionaryAddValue(query, kSecAttrLabel, label);
1348 CFReleaseSafe(label);
1349 }
1350 // NOTE: we always expect to use HTTPS for web forms.
1351 CFDictionaryAddValue(query, kSecAttrProtocol, kSecAttrProtocolHTTPS);
1352
1353 CFDataRef credential = CFStringCreateExternalRepresentation(kCFAllocatorDefault, password, kCFStringEncodingUTF8, 0);
1354 CFDictionarySetValue(query, kSecValueData, credential);
1355 CFReleaseSafe(credential);
1356 CFDictionarySetValue(query, kSecAttrComment, kSecSafariDefaultComment);
1357
1358 CFReleaseSafe(accessGroups);
1359 accessGroups = CFArrayCreate(kCFAllocatorDefault, (const void **)&kSecSafariAccessGroup, 1, &kCFTypeArrayCallBacks);
1360
1361 // mark the item as created by this function
1362 const int32_t creator_value = 'swca';
1363 CFNumberRef creator = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &creator_value);
1364 if (creator) {
1365 CFDictionarySetValue(query, kSecAttrCreator, creator);
1366 CFReleaseSafe(creator);
1367 ok = true;
1368 }
1369 else {
1370 // confirm the add
1371 // (per rdar://16680019, we won't prompt here in the normal case)
1372 ok = /*approved ||*/ swca_confirm_operation(swca_add_request_id, clientAuditToken, query, error,
1373 ^void (CFStringRef fqdn) { _SecAddNegativeWebCredential(fqdn, appID, false); });
1374 }
1375 }
1376 if (ok) {
1377 ok = _SecItemAdd(query, accessGroups, result, error);
1378 }
1379
1380 cleanup:
1381 #if 0 /* debugging */
1382 {
1383 const char *op_str = (password) ? ((update) ? "updated" : "added") : "deleted";
1384 const char *result_str = (ok) ? "true" : "false";
1385 secerror("result=%s, %s item %@, error=%@", result_str, op_str, *result, *error);
1386 }
1387 #else
1388 (void)update;
1389 #endif
1390 CFReleaseSafe(attrs);
1391 CFReleaseSafe(query);
1392 CFReleaseSafe(accessGroups);
1393 CFReleaseSafe(fqdn);
1394 return ok;
1395 }
1396
1397 /* Specialized version of SecItemCopyMatching for shared web credentials */
1398 bool
1399 _SecCopySharedWebCredential(CFDictionaryRef query,
1400 const audit_token_t *clientAuditToken,
1401 CFStringRef appID,
1402 CFArrayRef domains,
1403 CFTypeRef *result,
1404 CFErrorRef *error) {
1405
1406 CFMutableArrayRef credentials = NULL;
1407 CFMutableArrayRef foundItems = NULL;
1408 CFMutableArrayRef fqdns = NULL;
1409 CFArrayRef accessGroups = NULL;
1410 CFStringRef fqdn = NULL;
1411 CFStringRef account = NULL;
1412 CFIndex idx, count;
1413 SInt32 port = -1;
1414 bool ok = false;
1415
1416 require_quiet(result, cleanup);
1417 credentials = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
1418 foundItems = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
1419 fqdns = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
1420
1421 // give ourselves access to see matching items for kSecSafariAccessGroup
1422 CFStringRef accessGroup = CFSTR("*");
1423 accessGroups = CFArrayCreate(kCFAllocatorDefault, (const void **)&accessGroup, 1, &kCFTypeArrayCallBacks);
1424
1425 // On input, the query dictionary contains optional fqdn and account entries.
1426 fqdn = CFDictionaryGetValue(query, kSecAttrServer);
1427 account = CFDictionaryGetValue(query, kSecAttrAccount);
1428
1429 // Check autofill enabled status
1430 if (!swca_autofill_enabled(clientAuditToken)) {
1431 SecError(errSecBadReq, error, CFSTR("Autofill is not enabled in Safari settings"));
1432 goto cleanup;
1433 }
1434
1435 // Check fqdn; if NULL, add domains from caller's entitlement.
1436 if (fqdn) {
1437 CFArrayAppendValue(fqdns, fqdn);
1438 }
1439 else if (domains) {
1440 CFIndex idx, count = CFArrayGetCount(domains);
1441 for (idx=0; idx < count; idx++) {
1442 CFStringRef str = (CFStringRef) CFArrayGetValueAtIndex(domains, idx);
1443 // Parse the entry for our service label prefix
1444 if (str && CFStringHasPrefix(str, kSecSharedWebCredentialsService)) {
1445 CFIndex prefix_len = CFStringGetLength(kSecSharedWebCredentialsService)+1;
1446 CFIndex substr_len = CFStringGetLength(str) - prefix_len;
1447 CFRange range = { prefix_len, substr_len };
1448 fqdn = CFStringCreateWithSubstring(kCFAllocatorDefault, str, range);
1449 if (fqdn) {
1450 CFArrayAppendValue(fqdns, fqdn);
1451 CFRelease(fqdn);
1452 }
1453 }
1454 }
1455 }
1456 count = CFArrayGetCount(fqdns);
1457 if (count < 1) {
1458 SecError(errSecParam, error, CFSTR("No domain provided"));
1459 goto cleanup;
1460 }
1461
1462 // Aggregate search results for each domain
1463 for (idx = 0; idx < count; idx++) {
1464 CFMutableArrayRef items = NULL;
1465 CFMutableDictionaryRef attrs = NULL;
1466 fqdn = (CFStringRef) CFArrayGetValueAtIndex(fqdns, idx);
1467 CFRetainSafe(fqdn);
1468 port = -1;
1469
1470 // Parse the fqdn for a possible port specifier.
1471 if (fqdn) {
1472 CFStringRef urlStr = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@%@"), kSecSharedCredentialUrlScheme, fqdn);
1473 if (urlStr) {
1474 CFURLRef url = CFURLCreateWithString(kCFAllocatorDefault, urlStr, nil);
1475 if (url) {
1476 CFStringRef hostname = CFURLCopyHostName(url);
1477 if (hostname) {
1478 CFReleaseSafe(fqdn);
1479 fqdn = hostname;
1480 port = CFURLGetPortNumber(url);
1481 }
1482 CFReleaseSafe(url);
1483 }
1484 CFReleaseSafe(urlStr);
1485 }
1486 }
1487
1488 #if TARGET_IPHONE_SIMULATOR
1489 secerror("app/site association entitlements not checked in Simulator");
1490 #else
1491 OSStatus status = errSecMissingEntitlement;
1492 if (!appID) {
1493 SecError(status, error, CFSTR("Missing application-identifier entitlement"));
1494 CFReleaseSafe(fqdn);
1495 goto cleanup;
1496 }
1497 // validate that fqdn is part of caller's entitlement
1498 if (_SecEntitlementContainsDomainForService(domains, fqdn, kSecSharedWebCredentialsService)) {
1499 status = errSecSuccess;
1500 }
1501 if (errSecSuccess != status) {
1502 CFStringRef msg = CFStringCreateWithFormat(kCFAllocatorDefault, NULL,
1503 CFSTR("%@ not found in %@ entitlement"), fqdn, kSecEntitlementAssociatedDomains);
1504 if (!msg) {
1505 msg = CFRetain(CFSTR("Requested domain not found in entitlement"));
1506 }
1507 SecError(status, error, CFSTR("%@"), msg);
1508 CFReleaseSafe(msg);
1509 CFReleaseSafe(fqdn);
1510 goto cleanup;
1511 }
1512 #endif
1513
1514 attrs = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1515 if (!attrs) {
1516 SecError(errSecAllocate, error, CFSTR("Unable to create query dictionary"));
1517 CFReleaseSafe(fqdn);
1518 goto cleanup;
1519 }
1520 CFDictionaryAddValue(attrs, kSecClass, kSecClassInternetPassword);
1521 CFDictionaryAddValue(attrs, kSecAttrAccessGroup, kSecSafariAccessGroup);
1522 CFDictionaryAddValue(attrs, kSecAttrAuthenticationType, kSecAttrAuthenticationTypeHTMLForm);
1523 CFDictionaryAddValue(attrs, kSecAttrServer, fqdn);
1524 if (account) {
1525 CFDictionaryAddValue(attrs, kSecAttrAccount, account);
1526 }
1527 if (port < -1 || port > 0) {
1528 SInt16 portValueShort = (port & 0xFFFF);
1529 CFNumberRef portNumber = CFNumberCreate(NULL, kCFNumberSInt16Type, &portValueShort);
1530 CFDictionaryAddValue(attrs, kSecAttrPort, portNumber);
1531 CFReleaseSafe(portNumber);
1532 }
1533 CFDictionaryAddValue(attrs, kSecAttrSynchronizable, kCFBooleanTrue);
1534 CFDictionaryAddValue(attrs, kSecMatchLimit, kSecMatchLimitAll);
1535 CFDictionaryAddValue(attrs, kSecReturnAttributes, kCFBooleanTrue);
1536 CFDictionaryAddValue(attrs, kSecReturnData, kCFBooleanTrue);
1537
1538 ok = _SecItemCopyMatching(attrs, accessGroups, (CFTypeRef*)&items, error);
1539 if (count > 1) {
1540 // ignore interim error since we have multiple domains to search
1541 CFReleaseNull(*error);
1542 }
1543 if (ok && items && CFGetTypeID(items) == CFArrayGetTypeID()) {
1544 #if TARGET_IPHONE_SIMULATOR
1545 secerror("Ignoring app/site approval state in the Simulator.");
1546 bool approved = true;
1547 #else
1548 // get approval status for this app/domain pair
1549 SWCFlags flags = _SecAppDomainApprovalStatus(appID, fqdn, error);
1550 if (count > 1) {
1551 // ignore interim error since we have multiple domains to check
1552 CFReleaseNull(*error);
1553 }
1554 bool approved = (flags & kSWCFlag_SiteApproved);
1555 #endif
1556 if (approved) {
1557 CFArrayAppendArray(foundItems, items, CFRangeMake(0, CFArrayGetCount(items)));
1558 }
1559 }
1560 CFReleaseSafe(items);
1561 CFReleaseSafe(attrs);
1562 CFReleaseSafe(fqdn);
1563 }
1564
1565 // If matching credentials are found, the credentials provided to the completionHandler
1566 // will be a CFArrayRef containing CFDictionaryRef entries. Each dictionary entry will
1567 // contain the following pairs (see Security/SecItem.h):
1568 // key: kSecAttrServer value: CFStringRef (the website)
1569 // key: kSecAttrAccount value: CFStringRef (the account)
1570 // key: kSecSharedPassword value: CFStringRef (the password)
1571 // Optional keys:
1572 // key: kSecAttrPort value: CFNumberRef (the port number, if non-standard for https)
1573
1574 count = CFArrayGetCount(foundItems);
1575 for (idx = 0; idx < count; idx++) {
1576 CFDictionaryRef dict = (CFDictionaryRef) CFArrayGetValueAtIndex(foundItems, idx);
1577 CFMutableDictionaryRef newdict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1578 if (newdict && dict && CFGetTypeID(dict) == CFDictionaryGetTypeID()) {
1579 CFStringRef srvr = CFDictionaryGetValue(dict, kSecAttrServer);
1580 CFStringRef acct = CFDictionaryGetValue(dict, kSecAttrAccount);
1581 CFNumberRef pnum = CFDictionaryGetValue(dict, kSecAttrPort);
1582 CFStringRef icmt = CFDictionaryGetValue(dict, kSecAttrComment);
1583 CFDataRef data = CFDictionaryGetValue(dict, kSecValueData);
1584 if (srvr) {
1585 CFDictionaryAddValue(newdict, kSecAttrServer, srvr);
1586 }
1587 if (acct) {
1588 CFDictionaryAddValue(newdict, kSecAttrAccount, acct);
1589 }
1590 if (pnum) {
1591 SInt16 pval = -1;
1592 if (CFNumberGetValue(pnum, kCFNumberSInt16Type, &pval) &&
1593 (pval < -1 || pval > 0)) {
1594 CFDictionaryAddValue(newdict, kSecAttrPort, pnum);
1595 }
1596 }
1597 if (data) {
1598 CFStringRef password = CFStringCreateFromExternalRepresentation(kCFAllocatorDefault, data, kCFStringEncodingUTF8);
1599 if (password) {
1600 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1601 CFDictionaryAddValue(newdict, kSecSharedPassword, password);
1602 #else
1603 CFDictionaryAddValue(newdict, CFSTR("spwd"), password);
1604 #endif
1605 CFReleaseSafe(password);
1606 }
1607 }
1608 if (icmt && CFEqual(icmt, kSecSafariDefaultComment)) {
1609 CFArrayInsertValueAtIndex(credentials, 0, newdict);
1610 } else {
1611 CFArrayAppendValue(credentials, newdict);
1612 }
1613 }
1614 CFReleaseSafe(newdict);
1615 }
1616
1617 if (count) {
1618
1619 ok = false;
1620
1621 // create a new array of dictionaries (without the actual password) for picker UI
1622 count = CFArrayGetCount(credentials);
1623 CFMutableArrayRef items = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
1624 for (idx = 0; idx < count; idx++) {
1625 CFDictionaryRef dict = (CFDictionaryRef) CFArrayGetValueAtIndex(credentials, idx);
1626 CFMutableDictionaryRef newdict = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, dict);
1627 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1628 CFDictionaryRemoveValue(newdict, kSecSharedPassword);
1629 #else
1630 CFDictionaryRemoveValue(newdict, CFSTR("spwd"));
1631 #endif
1632 CFArrayAppendValue(items, newdict);
1633 CFReleaseSafe(newdict);
1634 }
1635
1636 // prompt user to select one of the dictionary items
1637 CFDictionaryRef selected = swca_copy_selected_dictionary(swca_select_request_id,
1638 clientAuditToken, items, error);
1639 if (selected) {
1640 // find the matching item in our credentials array
1641 CFStringRef srvr = CFDictionaryGetValue(selected, kSecAttrServer);
1642 CFStringRef acct = CFDictionaryGetValue(selected, kSecAttrAccount);
1643 CFNumberRef pnum = CFDictionaryGetValue(selected, kSecAttrPort);
1644 for (idx = 0; idx < count; idx++) {
1645 CFDictionaryRef dict = (CFDictionaryRef) CFArrayGetValueAtIndex(credentials, idx);
1646 CFStringRef srvr1 = CFDictionaryGetValue(dict, kSecAttrServer);
1647 CFStringRef acct1 = CFDictionaryGetValue(dict, kSecAttrAccount);
1648 CFNumberRef pnum1 = CFDictionaryGetValue(dict, kSecAttrPort);
1649
1650 if (!srvr || !srvr1 || !CFEqual(srvr, srvr1)) continue;
1651 if (!acct || !acct1 || !CFEqual(acct, acct1)) continue;
1652 if ((pnum && pnum1) && !CFEqual(pnum, pnum1)) continue;
1653
1654 // we have a match!
1655 CFReleaseSafe(selected);
1656 CFRetainSafe(dict);
1657 selected = dict;
1658 ok = true;
1659 break;
1660 }
1661 }
1662 CFReleaseSafe(items);
1663 CFArrayRemoveAllValues(credentials);
1664 if (selected && ok) {
1665 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1666 fqdn = CFDictionaryGetValue(selected, kSecAttrServer);
1667 #endif
1668 CFArrayAppendValue(credentials, selected);
1669 }
1670
1671 #if 0
1672 // confirm the access
1673 ok = swca_confirm_operation(swca_copy_request_id, clientAuditToken, query, error,
1674 ^void (CFStringRef fqdn) { _SecAddNegativeWebCredential(fqdn, appID, false); });
1675 #endif
1676 if (ok) {
1677 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1678 // register confirmation with database
1679 OSStatus status = _SecSWCEnsuredInitialized();
1680 if (status) {
1681 SecError(status, error, CFSTR("SWC initialize failed"));
1682 ok = false;
1683 CFReleaseSafe(selected);
1684 goto cleanup;
1685 }
1686 CFRetainSafe(appID);
1687 CFRetainSafe(fqdn);
1688 if (0 != sSWCSetServiceFlags_f(kSecSharedWebCredentialsService,
1689 appID, fqdn, kSWCFlag_ExternalMask, kSWCFlag_UserApproved,
1690 ^void(OSStatus inStatus, SWCFlags inNewFlags){
1691 CFReleaseSafe(appID);
1692 CFReleaseSafe(fqdn);
1693 }))
1694 {
1695 // we didn't queue the block
1696 CFReleaseSafe(appID);
1697 CFReleaseSafe(fqdn);
1698 }
1699 #endif
1700 }
1701 CFReleaseSafe(selected);
1702 }
1703 else if (NULL == *error) {
1704 // found no items, and we haven't already filled in the error
1705 SecError(errSecItemNotFound, error, CFSTR("no matching items found"));
1706 }
1707
1708 cleanup:
1709 if (!ok) {
1710 CFArrayRemoveAllValues(credentials);
1711 }
1712 CFReleaseSafe(foundItems);
1713 *result = credentials;
1714 CFReleaseSafe(accessGroups);
1715 CFReleaseSafe(fqdns);
1716 #if 0 /* debugging */
1717 secerror("result=%s, copied items %@, error=%@", (ok) ? "true" : "false", *result, *error);
1718 #endif
1719 return ok;
1720 }
1721
1722 // MARK: -
1723 // MARK: Keychain backup
1724
1725 CF_RETURNS_RETAINED CFDataRef
1726 _SecServerKeychainBackup(CFDataRef keybag, CFDataRef passcode, CFErrorRef *error) {
1727 CFDataRef backup;
1728 SecDbConnectionRef dbt = SecDbConnectionAquire(kc_dbhandle(), false, error);
1729
1730 if (!dbt)
1731 return NULL;
1732
1733 if (keybag == NULL && passcode == NULL) {
1734 #if USE_KEYSTORE
1735 backup = SecServerExportBackupableKeychain(dbt, KEYBAG_DEVICE, backup_keybag_handle, error);
1736 #else /* !USE_KEYSTORE */
1737 SecError(errSecParam, error, CFSTR("Why are you doing this?"));
1738 backup = NULL;
1739 #endif /* USE_KEYSTORE */
1740 } else {
1741 backup = SecServerKeychainBackup(dbt, keybag, passcode, error);
1742 }
1743
1744 SecDbConnectionRelease(dbt);
1745
1746 return backup;
1747 }
1748
1749 bool
1750 _SecServerKeychainRestore(CFDataRef backup, CFDataRef keybag, CFDataRef passcode, CFErrorRef *error) {
1751 if (backup == NULL || keybag == NULL)
1752 return SecError(errSecParam, error, CFSTR("backup or keybag missing"));
1753
1754 __block bool ok = true;
1755 ok &= SecDbPerformWrite(kc_dbhandle(), error, ^(SecDbConnectionRef dbconn) {
1756 ok = SecServerKeychainRestore(dbconn, backup, keybag, passcode, error);
1757 });
1758
1759 if (ok) {
1760 SecKeychainChanged(true);
1761 }
1762
1763 return ok;
1764 }
1765
1766
1767 // MARK: -
1768 // MARK: SecItemDataSource
1769
1770 // Make sure to call this before any writes to the keychain, so that we fire
1771 // up the engines to monitor manifest changes.
1772 SOSDataSourceFactoryRef SecItemDataSourceFactoryGetDefault(void) {
1773 return SecItemDataSourceFactoryGetShared(kc_dbhandle());
1774 }
1775
1776 /* AUDIT[securityd]:
1777 args_in (ok) is a caller provided, CFDictionaryRef.
1778 */
1779
1780 CF_RETURNS_RETAINED CFArrayRef
1781 _SecServerKeychainSyncUpdateMessage(CFDictionaryRef updates, CFErrorRef *error) {
1782 // This never fails, trust us!
1783 return SOSCCHandleUpdateMessage(updates);
1784 }
1785
1786 //
1787 // Truthiness in the cloud backup/restore support.
1788 //
1789
1790 static CFDictionaryRef
1791 _SecServerCopyTruthInTheCloud(CFDataRef keybag, CFDataRef password,
1792 CFDictionaryRef backup, CFErrorRef *error)
1793 {
1794 SOSManifestRef mold = NULL, mnow = NULL, mdelete = NULL, madd = NULL;
1795 __block CFMutableDictionaryRef backup_new = NULL;
1796 keybag_handle_t bag_handle;
1797 if (!ks_open_keybag(keybag, password, &bag_handle, error))
1798 return backup_new;
1799
1800 // We need to have a datasource singleton for protection domain
1801 // kSecAttrAccessibleWhenUnlocked and keep a single shared engine
1802 // instance around which we create in the datasource constructor as well.
1803 SOSDataSourceFactoryRef dsf = SecItemDataSourceFactoryGetDefault();
1804 SOSDataSourceRef ds = SOSDataSourceFactoryCreateDataSource(dsf, kSecAttrAccessibleWhenUnlocked, error);
1805 if (ds) {
1806 backup_new = backup ? CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, backup) : CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1807 mold = SOSCreateManifestWithBackup(backup, error);
1808 SOSEngineRef engine = SOSDataSourceGetSharedEngine(ds, error);
1809 mnow = SOSEngineCopyManifest(engine, NULL);
1810 if (!mnow) {
1811 mnow = SOSDataSourceCopyManifestWithViewNameSet(ds, SOSViewsGetV0ViewSet(), error);
1812 }
1813 if (!mnow) {
1814 CFReleaseNull(backup_new);
1815 secerror("failed to obtain manifest for keychain: %@", error ? *error : NULL);
1816 } else {
1817 SOSManifestDiff(mold, mnow, &mdelete, &madd, error);
1818 }
1819
1820 // Delete everything from the new_backup that is no longer in the datasource according to the datasources manifest.
1821 SOSManifestForEach(mdelete, ^(CFDataRef digest_data, bool *stop) {
1822 CFStringRef deleted_item_key = CFDataCopyHexString(digest_data);
1823 CFDictionaryRemoveValue(backup_new, deleted_item_key);
1824 CFRelease(deleted_item_key);
1825 });
1826
1827 CFMutableArrayRef changes = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
1828 SOSDataSourceForEachObject(ds, madd, error, ^void(CFDataRef digest, SOSObjectRef object, bool *stop) {
1829 CFErrorRef localError = NULL;
1830 CFDataRef digest_data = NULL;
1831 CFTypeRef value = NULL;
1832 if (!object) {
1833 // Key in our manifest can't be found in db, remove it from our manifest
1834 SOSChangesAppendDelete(changes, digest);
1835 } else if (!(digest_data = SOSObjectCopyDigest(ds, object, &localError))
1836 || !(value = SOSObjectCopyBackup(ds, object, bag_handle, &localError))) {
1837 if (SecErrorGetOSStatus(localError) == errSecDecode) {
1838 // Ignore decode errors, pretend the objects aren't there
1839 CFRelease(localError);
1840 // Object undecodable, remove it from our manifest
1841 SOSChangesAppendDelete(changes, digest);
1842 } else {
1843 // Stop iterating and propagate out all other errors.
1844 *stop = true;
1845 *error = localError;
1846 CFReleaseNull(backup_new);
1847 }
1848 } else {
1849 // TODO: Should we skip tombstones here?
1850 CFStringRef key = CFDataCopyHexString(digest_data);
1851 CFDictionarySetValue(backup_new, key, value);
1852 CFReleaseSafe(key);
1853 }
1854 CFReleaseSafe(digest_data);
1855 CFReleaseSafe(value);
1856 }) || CFReleaseNull(backup_new);
1857
1858 if (CFArrayGetCount(changes)) {
1859 if (!SOSEngineUpdateChanges(engine, kSOSDataSourceSOSTransaction, changes, error)) {
1860 CFReleaseNull(backup_new);
1861 }
1862 }
1863 CFReleaseSafe(changes);
1864
1865 SOSDataSourceRelease(ds, error) || CFReleaseNull(backup_new);
1866 }
1867
1868 CFReleaseSafe(mold);
1869 CFReleaseSafe(mnow);
1870 CFReleaseSafe(madd);
1871 CFReleaseSafe(mdelete);
1872 ks_close_keybag(bag_handle, error) || CFReleaseNull(backup_new);
1873
1874 return backup_new;
1875 }
1876
1877 static bool
1878 _SecServerRestoreTruthInTheCloud(CFDataRef keybag, CFDataRef password, CFDictionaryRef backup_in, CFErrorRef *error) {
1879 __block bool ok = true;
1880 keybag_handle_t bag_handle;
1881 if (!ks_open_keybag(keybag, password, &bag_handle, error))
1882 return false;
1883
1884 SOSManifestRef mbackup = SOSCreateManifestWithBackup(backup_in, error);
1885 if (mbackup) {
1886 SOSDataSourceFactoryRef dsf = SecItemDataSourceFactoryGetDefault();
1887 SOSDataSourceRef ds = SOSDataSourceFactoryCreateDataSource(dsf, kSecAttrAccessibleWhenUnlocked, error);
1888 ok &= ds && SOSDataSourceWith(ds, error, ^(SOSTransactionRef txn, bool *commit) {
1889 SOSManifestRef mnow = SOSDataSourceCopyManifestWithViewNameSet(ds, SOSViewsGetV0BackupViewSet(), error);
1890 SOSManifestRef mdelete = NULL, madd = NULL;
1891 SOSManifestDiff(mnow, mbackup, &mdelete, &madd, error);
1892
1893 // Don't delete everything in datasource not in backup.
1894
1895 // Add items from the backup
1896 SOSManifestForEach(madd, ^void(CFDataRef e, bool *stop) {
1897 CFDictionaryRef item = NULL;
1898 CFStringRef sha1 = CFDataCopyHexString(e);
1899 if (sha1) {
1900 item = CFDictionaryGetValue(backup_in, sha1);
1901 CFRelease(sha1);
1902 }
1903 if (item) {
1904 CFErrorRef localError = NULL;
1905
1906 if (!SOSObjectRestoreObject(ds, txn, bag_handle, item, &localError)) {
1907 OSStatus status = SecErrorGetOSStatus(localError);
1908 if (status == errSecDuplicateItem) {
1909 // Log and ignore duplicate item errors during restore
1910 secnotice("titc", "restore %@ not replacing existing item", item);
1911 } else if (status == errSecDecode) {
1912 // Log and ignore corrupted item errors during restore
1913 secnotice("titc", "restore %@ skipping corrupted item %@", item, localError);
1914 } else {
1915 if (status == errSecInteractionNotAllowed)
1916 *stop = true;
1917 // Propagate the first other error upwards (causing the restore to fail).
1918 secerror("restore %@ failed %@", item, localError);
1919 ok = false;
1920 if (error && !*error) {
1921 *error = localError;
1922 localError = NULL;
1923 }
1924 }
1925 CFReleaseSafe(localError);
1926 }
1927 }
1928 });
1929 ok &= SOSDataSourceRelease(ds, error);
1930 CFReleaseNull(mdelete);
1931 CFReleaseNull(madd);
1932 CFReleaseNull(mnow);
1933 });
1934 CFRelease(mbackup);
1935 }
1936
1937 ok &= ks_close_keybag(bag_handle, error);
1938
1939 return ok;
1940 }
1941
1942
1943 CF_RETURNS_RETAINED CFDictionaryRef
1944 _SecServerBackupSyncable(CFDictionaryRef backup, CFDataRef keybag, CFDataRef password, CFErrorRef *error) {
1945 require_action_quiet(isData(keybag), errOut, SecError(errSecParam, error, CFSTR("keybag %@ not a data"), keybag));
1946 require_action_quiet(!backup || isDictionary(backup), errOut, SecError(errSecParam, error, CFSTR("backup %@ not a dictionary"), backup));
1947 require_action_quiet(!password || isData(password), errOut, SecError(errSecParam, error, CFSTR("password %@ not a data"), password));
1948
1949 return _SecServerCopyTruthInTheCloud(keybag, password, backup, error);
1950
1951 errOut:
1952 return NULL;
1953 }
1954
1955 bool
1956 _SecServerRestoreSyncable(CFDictionaryRef backup, CFDataRef keybag, CFDataRef password, CFErrorRef *error) {
1957 bool ok;
1958 require_action_quiet(isData(keybag), errOut, ok = SecError(errSecParam, error, CFSTR("keybag %@ not a data"), keybag));
1959 require_action_quiet(isDictionary(backup), errOut, ok = SecError(errSecParam, error, CFSTR("backup %@ not a dictionary"), backup));
1960 if (password) {
1961 require_action_quiet(isData(password), errOut, ok = SecError(errSecParam, error, CFSTR("password not a data")));
1962 }
1963
1964 ok = _SecServerRestoreTruthInTheCloud(keybag, password, backup, error);
1965
1966 errOut:
1967 return ok;
1968 }
1969
1970 bool _SecServerRollKeys(bool force, CFErrorRef *error) {
1971 #if USE_KEYSTORE
1972 uint32_t keystore_generation_status = 0;
1973 if (aks_generation(KEYBAG_DEVICE, generation_noop, &keystore_generation_status))
1974 return false;
1975 uint32_t current_generation = keystore_generation_status & generation_current;
1976
1977 return kc_with_dbt(true, error, ^(SecDbConnectionRef dbt) {
1978 bool up_to_date = s3dl_dbt_keys_current(dbt, current_generation, NULL);
1979
1980 if (force && !up_to_date) {
1981 up_to_date = s3dl_dbt_update_keys(dbt, error);
1982 if (up_to_date) {
1983 secerror("Completed roll keys.");
1984 up_to_date = s3dl_dbt_keys_current(dbt, current_generation, NULL);
1985 }
1986 if (!up_to_date)
1987 secerror("Failed to roll keys.");
1988 }
1989 return up_to_date;
1990 });
1991 #else
1992 return true;
1993 #endif
1994 }