]> git.saurik.com Git - apple/security.git/blob - OSX/sec/securityd/SecItemServer.c
3e1f1565dea8d95f8a767fda4646f364fdf598bc
[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 void (*SecTaskDiagnoseEntitlements)(CFArrayRef accessGroups) = NULL;
715
716 /* AUDIT[securityd](done):
717 query (ok) is a caller provided dictionary, only its cf type has been checked.
718 */
719 static bool
720 SecItemServerCopyMatching(CFDictionaryRef query, CFTypeRef *result,
721 CFArrayRef accessGroups, CFErrorRef *error)
722 {
723 CFIndex ag_count;
724 if (!accessGroups || 0 == (ag_count = CFArrayGetCount(accessGroups))) {
725 if (SecTaskDiagnoseEntitlements)
726 SecTaskDiagnoseEntitlements(accessGroups);
727 return SecError(errSecMissingEntitlement, error,
728 CFSTR("client has neither application-identifier nor keychain-access-groups entitlements"));
729 }
730
731 if (CFArrayContainsValue(accessGroups, CFRangeMake(0, ag_count), CFSTR("*"))) {
732 /* Having the special accessGroup "*" allows access to all accessGroups. */
733 accessGroups = NULL;
734 }
735
736 bool ok = false;
737 Query *q = query_create_with_limit(query, 1, error);
738 if (q) {
739 CFStringRef agrp = CFDictionaryGetValue(q->q_item, kSecAttrAccessGroup);
740 if (agrp && accessGroupsAllows(accessGroups, agrp)) {
741 // TODO: Return an error if agrp is not NULL and accessGroupsAllows() fails above.
742 const void *val = agrp;
743 accessGroups = CFArrayCreate(0, &val, 1, &kCFTypeArrayCallBacks);
744 } else {
745 CFRetainSafe(accessGroups);
746 }
747
748 query_set_caller_access_groups(q, accessGroups);
749
750 /* Sanity check the query. */
751 if (q->q_use_item_list) {
752 ok = SecError(errSecUseItemListUnsupported, error, CFSTR("use item list unsupported"));
753 #if defined(MULTIPLE_KEYCHAINS)
754 } else if (q->q_use_keychain) {
755 ok = SecError(errSecUseKeychainUnsupported, error, CFSTR("use keychain list unsupported"));
756 #endif
757 } else if (q->q_match_issuer && ((q->q_class != &cert_class) &&
758 (q->q_class != &identity_class))) {
759 ok = SecError(errSecUnsupportedOperation, error, CFSTR("unsupported match attribute"));
760 } else if (q->q_return_type != 0 && result == NULL) {
761 ok = SecError(errSecReturnMissingPointer, error, CFSTR("missing pointer"));
762 } else if (!q->q_error) {
763 ok = kc_with_dbt(false, error, ^(SecDbConnectionRef dbt) {
764 return s3dl_copy_matching(dbt, q, result, accessGroups, error);
765 });
766 }
767
768 CFReleaseSafe(accessGroups);
769 if (!query_destroy(q, error))
770 ok = false;
771 }
772
773 return ok;
774 }
775
776 bool
777 _SecItemCopyMatching(CFDictionaryRef query, CFArrayRef accessGroups, CFTypeRef *result, CFErrorRef *error) {
778 return SecItemServerCopyMatching(query, result, accessGroups, error);
779 }
780
781 /* AUDIT[securityd](done):
782 attributes (ok) is a caller provided dictionary, only its cf type has
783 been checked.
784 */
785 bool
786 _SecItemAdd(CFDictionaryRef attributes, CFArrayRef accessGroups,
787 CFTypeRef *result, CFErrorRef *error)
788 {
789 bool ok = true;
790 CFIndex ag_count;
791 if (!accessGroups || 0 == (ag_count = CFArrayGetCount(accessGroups))) {
792 if (SecTaskDiagnoseEntitlements)
793 SecTaskDiagnoseEntitlements(accessGroups);
794 return SecError(errSecMissingEntitlement, error,
795 CFSTR("client has neither application-identifier nor keychain-access-groups entitlements"));
796 }
797
798 Query *q = query_create_with_limit(attributes, 0, error);
799 if (q) {
800 /* Access group sanity checking. */
801 CFStringRef agrp = (CFStringRef)CFDictionaryGetValue(attributes,
802 kSecAttrAccessGroup);
803
804 CFArrayRef ag = accessGroups;
805 /* Having the special accessGroup "*" allows access to all accessGroups. */
806 if (CFArrayContainsValue(accessGroups, CFRangeMake(0, ag_count), CFSTR("*")))
807 accessGroups = NULL;
808
809 if (agrp) {
810 /* The user specified an explicit access group, validate it. */
811 if (!accessGroupsAllows(accessGroups, agrp))
812 ok = SecError(errSecNoAccessForItem, error, CFSTR("NoAccessForItem"));
813 } else {
814 agrp = (CFStringRef)CFArrayGetValueAtIndex(ag, 0);
815
816 /* We are using an implicit access group, add it as if the user
817 specified it as an attribute. */
818 query_add_attribute(kSecAttrAccessGroup, agrp, q);
819 }
820
821 if (ok) {
822 query_ensure_access_control(q, agrp);
823
824 if (q->q_row_id)
825 ok = SecError(errSecValuePersistentRefUnsupported, error, CFSTR("q_row_id")); // TODO: better error string
826 #if defined(MULTIPLE_KEYCHAINS)
827 else if (q->q_use_keychain_list)
828 ok = SecError(errSecUseKeychainListUnsupported, error, CFSTR("q_use_keychain_list")); // TODO: better error string;
829 #endif
830 else if (!q->q_error) {
831 ok = kc_with_dbt(true, error, ^(SecDbConnectionRef dbt){
832 return kc_transaction(dbt, error, ^{
833 query_pre_add(q, true);
834 return s3dl_query_add(dbt, q, result, error);
835 });
836 });
837 }
838 }
839 ok = query_notify_and_destroy(q, ok, error);
840 } else {
841 ok = false;
842 }
843 return ok;
844 }
845
846 /* AUDIT[securityd](done):
847 query (ok) and attributesToUpdate (ok) are a caller provided dictionaries,
848 only their cf types have been checked.
849 */
850 bool
851 _SecItemUpdate(CFDictionaryRef query, CFDictionaryRef attributesToUpdate,
852 CFArrayRef accessGroups, CFErrorRef *error)
853 {
854 CFIndex ag_count;
855 if (!accessGroups || 0 == (ag_count = CFArrayGetCount(accessGroups))) {
856 if (SecTaskDiagnoseEntitlements)
857 SecTaskDiagnoseEntitlements(accessGroups);
858 return SecError(errSecMissingEntitlement, error,
859 CFSTR("client has neither application-identifier nor keychain-access-groups entitlements"));
860 }
861
862 if (CFArrayContainsValue(accessGroups, CFRangeMake(0, ag_count), CFSTR("*"))) {
863 /* Having the special accessGroup "*" allows access to all accessGroups. */
864 accessGroups = NULL;
865 }
866
867 bool ok = true;
868 Query *q = query_create_with_limit(query, kSecMatchUnlimited, error);
869 if (!q) {
870 ok = false;
871 }
872 if (ok) {
873 /* Sanity check the query. */
874 query_set_caller_access_groups(q, accessGroups);
875 if (q->q_use_item_list) {
876 ok = SecError(errSecUseItemListUnsupported, error, CFSTR("use item list not supported"));
877 } else if (q->q_return_type & kSecReturnDataMask) {
878 /* Update doesn't return anything so don't ask for it. */
879 ok = SecError(errSecReturnDataUnsupported, error, CFSTR("return data not supported by update"));
880 } else if (q->q_return_type & kSecReturnAttributesMask) {
881 ok = SecError(errSecReturnAttributesUnsupported, error, CFSTR("return attributes not supported by update"));
882 } else if (q->q_return_type & kSecReturnRefMask) {
883 ok = SecError(errSecReturnRefUnsupported, error, CFSTR("return ref not supported by update"));
884 } else if (q->q_return_type & kSecReturnPersistentRefMask) {
885 ok = SecError(errSecReturnPersistentRefUnsupported, error, CFSTR("return persistent ref not supported by update"));
886 } else {
887 /* Access group sanity checking. */
888 CFStringRef agrp = (CFStringRef)CFDictionaryGetValue(attributesToUpdate,
889 kSecAttrAccessGroup);
890 if (agrp) {
891 /* The user is attempting to modify the access group column,
892 validate it to make sure the new value is allowable. */
893 if (!accessGroupsAllows(accessGroups, agrp)) {
894 ok = SecError(errSecNoAccessForItem, error, CFSTR("accessGroup %@ not in %@"), agrp, accessGroups);
895 }
896 }
897 }
898 }
899 if (ok) {
900 ok = kc_with_dbt(true, error, ^(SecDbConnectionRef dbt) {
901 return s3dl_query_update(dbt, q, attributesToUpdate, accessGroups, error);
902 });
903 }
904 if (q) {
905 ok = query_notify_and_destroy(q, ok, error);
906 }
907 return ok;
908 }
909
910
911 /* AUDIT[securityd](done):
912 query (ok) is a caller provided dictionary, only its cf type has been checked.
913 */
914 bool
915 _SecItemDelete(CFDictionaryRef query, CFArrayRef accessGroups, CFErrorRef *error)
916 {
917 CFIndex ag_count;
918 if (!accessGroups || 0 == (ag_count = CFArrayGetCount(accessGroups))) {
919 if (SecTaskDiagnoseEntitlements)
920 SecTaskDiagnoseEntitlements(accessGroups);
921 return SecError(errSecMissingEntitlement, error,
922 CFSTR("client has neither application-identifier nor keychain-access-groups entitlements"));
923 }
924
925 if (CFArrayContainsValue(accessGroups, CFRangeMake(0, ag_count), CFSTR("*"))) {
926 /* Having the special accessGroup "*" allows access to all accessGroups. */
927 accessGroups = NULL;
928 }
929
930 Query *q = query_create_with_limit(query, kSecMatchUnlimited, error);
931 bool ok;
932 if (q) {
933 query_set_caller_access_groups(q, accessGroups);
934 /* Sanity check the query. */
935 if (q->q_limit != kSecMatchUnlimited)
936 ok = SecError(errSecMatchLimitUnsupported, error, CFSTR("match limit not supported by delete"));
937 else if (query_match_count(q) != 0)
938 ok = SecError(errSecItemMatchUnsupported, error, CFSTR("match not supported by delete"));
939 else if (q->q_ref)
940 ok = SecError(errSecValueRefUnsupported, error, CFSTR("value ref not supported by delete"));
941 else if (q->q_row_id && query_attr_count(q))
942 ok = SecError(errSecItemIllegalQuery, error, CFSTR("rowid and other attributes are mutually exclusive"));
943 else {
944 ok = kc_with_dbt(true, error, ^(SecDbConnectionRef dbt) {
945 return s3dl_query_delete(dbt, q, accessGroups, error);
946 });
947 }
948 ok = query_notify_and_destroy(q, ok, error);
949 } else {
950 ok = false;
951 }
952 return ok;
953 }
954
955
956 /* AUDIT[securityd](done):
957 No caller provided inputs.
958 */
959 static bool
960 SecItemServerDeleteAll(CFErrorRef *error) {
961 return kc_with_dbt(true, error, ^bool (SecDbConnectionRef dbt) {
962 return (kc_transaction(dbt, error, ^bool {
963 return (SecDbExec(dbt, CFSTR("DELETE from genp;"), error) &&
964 SecDbExec(dbt, CFSTR("DELETE from inet;"), error) &&
965 SecDbExec(dbt, CFSTR("DELETE from cert;"), error) &&
966 SecDbExec(dbt, CFSTR("DELETE from keys;"), error));
967 }) && SecDbExec(dbt, CFSTR("VACUUM;"), error));
968 });
969 }
970
971 bool
972 _SecItemDeleteAll(CFErrorRef *error) {
973 return SecItemServerDeleteAll(error);
974 }
975
976
977 // MARK: -
978 // MARK: Shared web credentials
979
980 /* constants */
981 #define SEC_CONST_DECL(k,v) const CFStringRef k = CFSTR(v);
982
983 SEC_CONST_DECL (kSecSafariAccessGroup, "com.apple.cfnetwork");
984 SEC_CONST_DECL (kSecSafariDefaultComment, "default");
985 SEC_CONST_DECL (kSecSafariPasswordsNotSaved, "Passwords not saved");
986 SEC_CONST_DECL (kSecSharedCredentialUrlScheme, "https://");
987 SEC_CONST_DECL (kSecSharedWebCredentialsService, "webcredentials");
988
989 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
990 static dispatch_once_t sSecSWCInitializeOnce = 0;
991 static void * sSecSWCLibrary = NULL;
992 static SWCCheckService_f sSWCCheckService_f = NULL;
993 static SWCSetServiceFlags_f sSWCSetServiceFlags_f = NULL;
994
995 static OSStatus _SecSWCEnsuredInitialized(void);
996
997 static OSStatus _SecSWCEnsuredInitialized(void)
998 {
999 __block OSStatus status = errSecNotAvailable;
1000
1001 dispatch_once(&sSecSWCInitializeOnce, ^{
1002 sSecSWCLibrary = dlopen("/System/Library/PrivateFrameworks/SharedWebCredentials.framework/SharedWebCredentials", RTLD_LAZY | RTLD_LOCAL);
1003 assert(sSecSWCLibrary);
1004 if (sSecSWCLibrary) {
1005 sSWCCheckService_f = (SWCCheckService_f)(uintptr_t) dlsym(sSecSWCLibrary, "SWCCheckService");
1006 sSWCSetServiceFlags_f = (SWCSetServiceFlags_f)(uintptr_t) dlsym(sSecSWCLibrary, "SWCSetServiceFlags");
1007 }
1008 });
1009
1010 if (sSWCCheckService_f && sSWCSetServiceFlags_f) {
1011 status = noErr;
1012 }
1013 return status;
1014 }
1015 #endif
1016
1017 #if !TARGET_IPHONE_SIMULATOR
1018 static SWCFlags
1019 _SecAppDomainApprovalStatus(CFStringRef appID, CFStringRef fqdn, CFErrorRef *error)
1020 {
1021 __block SWCFlags flags = kSWCFlags_None;
1022
1023 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1024 OSStatus status = _SecSWCEnsuredInitialized();
1025 if (status) {
1026 SecError(status, error, CFSTR("SWC initialize failed"));
1027 return flags;
1028 }
1029 CFRetainSafe(appID);
1030 CFRetainSafe(fqdn);
1031 dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
1032 dispatch_retain(semaphore);
1033 if (0 == sSWCCheckService_f(kSecSharedWebCredentialsService, appID, fqdn,
1034 ^void (OSStatus inStatus, SWCFlags inFlags, CFDictionaryRef inDetails) {
1035 if (!inStatus) { flags = inFlags; }
1036 CFReleaseSafe(appID);
1037 CFReleaseSafe(fqdn);
1038 dispatch_semaphore_signal(semaphore);
1039 dispatch_release(semaphore);
1040 //secerror("SWCCheckService: inStatus=%d, flags=%0X", inStatus, flags);
1041 }))
1042 {
1043 // wait for the block to complete, as we need its answer
1044 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
1045 }
1046 else // didn't queue the block
1047 {
1048 CFReleaseSafe(appID);
1049 CFReleaseSafe(fqdn);
1050 dispatch_release(semaphore);
1051 }
1052 dispatch_release(semaphore);
1053 #else
1054 flags |= (kSWCFlag_SiteApproved);
1055 #endif
1056
1057 if (!error) { return flags; }
1058 *error = NULL;
1059
1060 // check website approval status
1061 if (!(flags & kSWCFlag_SiteApproved)) {
1062 if (flags & kSWCFlag_Pending) {
1063 SecError(errSecAuthFailed, error, CFSTR("Approval is pending for \"%@\", try later"), fqdn);
1064 } else {
1065 SecError(errSecAuthFailed, error, CFSTR("\"%@\" failed to approve \"%@\""), fqdn, appID);
1066 }
1067 return flags;
1068 }
1069
1070 // check user approval status
1071 if (flags & kSWCFlag_UserDenied) {
1072 SecError(errSecAuthFailed, error, CFSTR("User denied access to \"%@\" by \"%@\""), fqdn, appID);
1073 }
1074 return flags;
1075 }
1076 #endif
1077
1078 #if !TARGET_IPHONE_SIMULATOR
1079 static bool
1080 _SecEntitlementContainsDomainForService(CFArrayRef domains, CFStringRef domain, CFStringRef service)
1081 {
1082 bool result = false;
1083 CFIndex idx, count = (domains) ? CFArrayGetCount(domains) : (CFIndex) 0;
1084 if (!count || !domain || !service) {
1085 return result;
1086 }
1087 for (idx=0; idx < count; idx++) {
1088 CFStringRef str = (CFStringRef) CFArrayGetValueAtIndex(domains, idx);
1089 if (str && CFStringHasPrefix(str, kSecSharedWebCredentialsService)) {
1090 CFIndex prefix_len = CFStringGetLength(kSecSharedWebCredentialsService)+1;
1091 CFIndex substr_len = CFStringGetLength(str) - prefix_len;
1092 CFRange range = { prefix_len, substr_len };
1093 CFStringRef substr = CFStringCreateWithSubstring(kCFAllocatorDefault, str, range);
1094 if (substr && CFEqual(substr, domain)) {
1095 result = true;
1096 }
1097 CFReleaseSafe(substr);
1098 if (result) {
1099 break;
1100 }
1101 }
1102 }
1103 return result;
1104 }
1105 #endif
1106
1107 static bool
1108 _SecAddNegativeWebCredential(CFStringRef fqdn, CFStringRef appID, bool forSafari)
1109 {
1110 bool result = false;
1111 if (!fqdn) { return result; }
1112
1113 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1114 OSStatus status = _SecSWCEnsuredInitialized();
1115 if (status) { return false; }
1116
1117 // update our database
1118 CFRetainSafe(appID);
1119 CFRetainSafe(fqdn);
1120 if (0 == sSWCSetServiceFlags_f(kSecSharedWebCredentialsService,
1121 appID, fqdn, kSWCFlag_ExternalMask, kSWCFlag_UserDenied,
1122 ^void(OSStatus inStatus, SWCFlags inNewFlags){
1123 CFReleaseSafe(appID);
1124 CFReleaseSafe(fqdn);
1125 }))
1126 {
1127 result = true;
1128 }
1129 else // didn't queue the block
1130 {
1131 CFReleaseSafe(appID);
1132 CFReleaseSafe(fqdn);
1133 }
1134 #endif
1135 if (!forSafari) { return result; }
1136
1137 // below this point: create a negative Safari web credential item
1138
1139 CFMutableDictionaryRef attrs = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1140 if (!attrs) { return result; }
1141
1142 CFErrorRef error = NULL;
1143 CFStringRef accessGroup = CFSTR("*");
1144 CFArrayRef accessGroups = CFArrayCreate(kCFAllocatorDefault, (const void **)&accessGroup, 1, &kCFTypeArrayCallBacks);
1145
1146 CFDictionaryAddValue(attrs, kSecClass, kSecClassInternetPassword);
1147 CFDictionaryAddValue(attrs, kSecAttrAccessGroup, kSecSafariAccessGroup);
1148 CFDictionaryAddValue(attrs, kSecAttrAuthenticationType, kSecAttrAuthenticationTypeHTMLForm);
1149 CFDictionaryAddValue(attrs, kSecAttrProtocol, kSecAttrProtocolHTTPS);
1150 CFDictionaryAddValue(attrs, kSecAttrServer, fqdn);
1151 CFDictionaryAddValue(attrs, kSecAttrSynchronizable, kCFBooleanTrue);
1152
1153 (void)_SecItemDelete(attrs, accessGroups, &error);
1154 CFReleaseNull(error);
1155
1156 CFDictionaryAddValue(attrs, kSecAttrAccount, kSecSafariPasswordsNotSaved);
1157 CFDictionaryAddValue(attrs, kSecAttrComment, kSecSafariDefaultComment);
1158
1159 CFStringRef label = CFStringCreateWithFormat(kCFAllocatorDefault,
1160 NULL, CFSTR("%@ (%@)"), fqdn, kSecSafariPasswordsNotSaved);
1161 if (label) {
1162 CFDictionaryAddValue(attrs, kSecAttrLabel, label);
1163 CFReleaseSafe(label);
1164 }
1165
1166 UInt8 space = ' ';
1167 CFDataRef data = CFDataCreate(kCFAllocatorDefault, &space, 1);
1168 if (data) {
1169 CFDictionarySetValue(attrs, kSecValueData, data);
1170 CFReleaseSafe(data);
1171 }
1172
1173 CFTypeRef addResult = NULL;
1174 result = _SecItemAdd(attrs, accessGroups, &addResult, &error);
1175
1176 CFReleaseSafe(addResult);
1177 CFReleaseSafe(error);
1178 CFReleaseSafe(attrs);
1179 CFReleaseSafe(accessGroups);
1180
1181 return result;
1182 }
1183
1184 /* Specialized version of SecItemAdd for shared web credentials */
1185 bool
1186 _SecAddSharedWebCredential(CFDictionaryRef attributes,
1187 const audit_token_t *clientAuditToken,
1188 CFStringRef appID,
1189 CFArrayRef domains,
1190 CFTypeRef *result,
1191 CFErrorRef *error) {
1192
1193 CFStringRef fqdn = CFDictionaryGetValue(attributes, kSecAttrServer);
1194 CFStringRef account = CFDictionaryGetValue(attributes, kSecAttrAccount);
1195 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1196 CFStringRef password = CFDictionaryGetValue(attributes, kSecSharedPassword);
1197 #else
1198 CFStringRef password = CFDictionaryGetValue(attributes, CFSTR("spwd"));
1199 #endif
1200 CFStringRef accessGroup = CFSTR("*");
1201 CFArrayRef accessGroups = NULL;
1202 CFMutableDictionaryRef query = NULL, attrs = NULL;
1203 SInt32 port = -1;
1204 bool ok = false, update = false;
1205 //bool approved = false;
1206
1207 // check autofill enabled status
1208 if (!swca_autofill_enabled(clientAuditToken)) {
1209 SecError(errSecBadReq, error, CFSTR("Autofill is not enabled in Safari settings"));
1210 goto cleanup;
1211 }
1212
1213 // parse fqdn with CFURL here, since it could be specified as domain:port
1214 if (fqdn) {
1215 CFRetainSafe(fqdn);
1216 CFStringRef urlStr = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@%@"), kSecSharedCredentialUrlScheme, fqdn);
1217 if (urlStr) {
1218 CFURLRef url = CFURLCreateWithString(kCFAllocatorDefault, urlStr, nil);
1219 if (url) {
1220 CFStringRef hostname = CFURLCopyHostName(url);
1221 if (hostname) {
1222 CFReleaseSafe(fqdn);
1223 fqdn = hostname;
1224 port = CFURLGetPortNumber(url);
1225 }
1226 CFReleaseSafe(url);
1227 }
1228 CFReleaseSafe(urlStr);
1229 }
1230 }
1231
1232 if (!account) {
1233 SecError(errSecParam, error, CFSTR("No account provided"));
1234 goto cleanup;
1235 }
1236 if (!fqdn) {
1237 SecError(errSecParam, error, CFSTR("No domain provided"));
1238 goto cleanup;
1239 }
1240
1241 #if TARGET_IPHONE_SIMULATOR
1242 secerror("app/site association entitlements not checked in Simulator");
1243 #else
1244 OSStatus status = errSecMissingEntitlement;
1245 // validate that fqdn is part of caller's shared credential domains entitlement
1246 if (!appID) {
1247 SecError(status, error, CFSTR("Missing application-identifier entitlement"));
1248 goto cleanup;
1249 }
1250 if (_SecEntitlementContainsDomainForService(domains, fqdn, kSecSharedWebCredentialsService)) {
1251 status = errSecSuccess;
1252 }
1253 if (errSecSuccess != status) {
1254 CFStringRef msg = CFStringCreateWithFormat(kCFAllocatorDefault, NULL,
1255 CFSTR("%@ not found in %@ entitlement"), fqdn, kSecEntitlementAssociatedDomains);
1256 if (!msg) {
1257 msg = CFRetain(CFSTR("Requested domain not found in entitlement"));
1258 }
1259 SecError(status, error, CFSTR("%@"), msg);
1260 CFReleaseSafe(msg);
1261 goto cleanup;
1262 }
1263 #endif
1264
1265 #if TARGET_IPHONE_SIMULATOR
1266 secerror("Ignoring app/site approval state in the Simulator.");
1267 #else
1268 // get approval status for this app/domain pair
1269 SWCFlags flags = _SecAppDomainApprovalStatus(appID, fqdn, error);
1270 //approved = ((flags & kSWCFlag_SiteApproved) && (flags & kSWCFlag_UserApproved));
1271 if (!(flags & kSWCFlag_SiteApproved)) {
1272 goto cleanup;
1273 }
1274 #endif
1275
1276 // give ourselves access to see matching items for kSecSafariAccessGroup
1277 accessGroups = CFArrayCreate(kCFAllocatorDefault, (const void **)&accessGroup, 1, &kCFTypeArrayCallBacks);
1278
1279 // create lookup query
1280 query = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1281 if (!query) {
1282 SecError(errSecAllocate, error, CFSTR("Unable to create query dictionary"));
1283 goto cleanup;
1284 }
1285 CFDictionaryAddValue(query, kSecClass, kSecClassInternetPassword);
1286 CFDictionaryAddValue(query, kSecAttrAccessGroup, kSecSafariAccessGroup);
1287 CFDictionaryAddValue(query, kSecAttrAuthenticationType, kSecAttrAuthenticationTypeHTMLForm);
1288 CFDictionaryAddValue(query, kSecAttrServer, fqdn);
1289 CFDictionaryAddValue(query, kSecAttrSynchronizable, kCFBooleanTrue);
1290
1291 // check for presence of Safari's negative entry ('passwords not saved')
1292 CFDictionarySetValue(query, kSecAttrAccount, kSecSafariPasswordsNotSaved);
1293 ok = _SecItemCopyMatching(query, accessGroups, result, error);
1294 CFReleaseNull(*result);
1295 CFReleaseNull(*error);
1296 if (ok) {
1297 SecError(errSecDuplicateItem, error, CFSTR("Item already exists for this server"));
1298 goto cleanup;
1299 }
1300
1301 // now use the provided account (and optional port number, if one was present)
1302 CFDictionarySetValue(query, kSecAttrAccount, account);
1303 if (port < -1 || port > 0) {
1304 SInt16 portValueShort = (port & 0xFFFF);
1305 CFNumberRef portNumber = CFNumberCreate(NULL, kCFNumberSInt16Type, &portValueShort);
1306 CFDictionaryAddValue(query, kSecAttrPort, portNumber);
1307 CFReleaseSafe(portNumber);
1308 }
1309
1310 // look up existing password
1311 if (_SecItemCopyMatching(query, accessGroups, result, error)) {
1312 // found it, so this becomes either an "update password" or "delete password" operation
1313 CFReleaseNull(*result);
1314 CFReleaseNull(*error);
1315 update = (password != NULL);
1316 if (update) {
1317 attrs = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1318 CFDataRef credential = CFStringCreateExternalRepresentation(kCFAllocatorDefault, password, kCFStringEncodingUTF8, 0);
1319 CFDictionaryAddValue(attrs, kSecValueData, credential);
1320 CFReleaseSafe(credential);
1321 CFDictionaryAddValue(attrs, kSecAttrComment, kSecSafariDefaultComment);
1322
1323 // confirm the update
1324 // (per rdar://16676310 we always prompt, even if there was prior user approval)
1325 ok = /*approved ||*/ swca_confirm_operation(swca_update_request_id, clientAuditToken, query, error,
1326 ^void (CFStringRef fqdn) { _SecAddNegativeWebCredential(fqdn, appID, false); });
1327 if (ok) {
1328 ok = _SecItemUpdate(query, attrs, accessGroups, error);
1329 }
1330 }
1331 else {
1332 // confirm the delete
1333 // (per rdar://16676288 we always prompt, even if there was prior user approval)
1334 ok = /*approved ||*/ swca_confirm_operation(swca_delete_request_id, clientAuditToken, query, error,
1335 ^void (CFStringRef fqdn) { _SecAddNegativeWebCredential(fqdn, appID, false); });
1336 if (ok) {
1337 ok = _SecItemDelete(query, accessGroups, error);
1338 }
1339 }
1340 if (ok) {
1341 CFReleaseNull(*error);
1342 }
1343 goto cleanup;
1344 }
1345 CFReleaseNull(*result);
1346 CFReleaseNull(*error);
1347
1348 // password does not exist, so prepare to add it
1349 if (!password) {
1350 // a NULL password value removes the existing credential. Since we didn't find it, this is a no-op.
1351 ok = true;
1352 goto cleanup;
1353 }
1354 else {
1355 CFStringRef label = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@ (%@)"), fqdn, account);
1356 if (label) {
1357 CFDictionaryAddValue(query, kSecAttrLabel, label);
1358 CFReleaseSafe(label);
1359 }
1360 // NOTE: we always expect to use HTTPS for web forms.
1361 CFDictionaryAddValue(query, kSecAttrProtocol, kSecAttrProtocolHTTPS);
1362
1363 CFDataRef credential = CFStringCreateExternalRepresentation(kCFAllocatorDefault, password, kCFStringEncodingUTF8, 0);
1364 CFDictionarySetValue(query, kSecValueData, credential);
1365 CFReleaseSafe(credential);
1366 CFDictionarySetValue(query, kSecAttrComment, kSecSafariDefaultComment);
1367
1368 CFReleaseSafe(accessGroups);
1369 accessGroups = CFArrayCreate(kCFAllocatorDefault, (const void **)&kSecSafariAccessGroup, 1, &kCFTypeArrayCallBacks);
1370
1371 // mark the item as created by this function
1372 const int32_t creator_value = 'swca';
1373 CFNumberRef creator = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &creator_value);
1374 if (creator) {
1375 CFDictionarySetValue(query, kSecAttrCreator, creator);
1376 CFReleaseSafe(creator);
1377 ok = true;
1378 }
1379 else {
1380 // confirm the add
1381 // (per rdar://16680019, we won't prompt here in the normal case)
1382 ok = /*approved ||*/ swca_confirm_operation(swca_add_request_id, clientAuditToken, query, error,
1383 ^void (CFStringRef fqdn) { _SecAddNegativeWebCredential(fqdn, appID, false); });
1384 }
1385 }
1386 if (ok) {
1387 ok = _SecItemAdd(query, accessGroups, result, error);
1388 }
1389
1390 cleanup:
1391 #if 0 /* debugging */
1392 {
1393 const char *op_str = (password) ? ((update) ? "updated" : "added") : "deleted";
1394 const char *result_str = (ok) ? "true" : "false";
1395 secerror("result=%s, %s item %@, error=%@", result_str, op_str, *result, *error);
1396 }
1397 #else
1398 (void)update;
1399 #endif
1400 CFReleaseSafe(attrs);
1401 CFReleaseSafe(query);
1402 CFReleaseSafe(accessGroups);
1403 CFReleaseSafe(fqdn);
1404 return ok;
1405 }
1406
1407 /* Specialized version of SecItemCopyMatching for shared web credentials */
1408 bool
1409 _SecCopySharedWebCredential(CFDictionaryRef query,
1410 const audit_token_t *clientAuditToken,
1411 CFStringRef appID,
1412 CFArrayRef domains,
1413 CFTypeRef *result,
1414 CFErrorRef *error) {
1415
1416 CFMutableArrayRef credentials = NULL;
1417 CFMutableArrayRef foundItems = NULL;
1418 CFMutableArrayRef fqdns = NULL;
1419 CFArrayRef accessGroups = NULL;
1420 CFStringRef fqdn = NULL;
1421 CFStringRef account = NULL;
1422 CFIndex idx, count;
1423 SInt32 port = -1;
1424 bool ok = false;
1425
1426 require_quiet(result, cleanup);
1427 credentials = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
1428 foundItems = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
1429 fqdns = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
1430
1431 // give ourselves access to see matching items for kSecSafariAccessGroup
1432 CFStringRef accessGroup = CFSTR("*");
1433 accessGroups = CFArrayCreate(kCFAllocatorDefault, (const void **)&accessGroup, 1, &kCFTypeArrayCallBacks);
1434
1435 // On input, the query dictionary contains optional fqdn and account entries.
1436 fqdn = CFDictionaryGetValue(query, kSecAttrServer);
1437 account = CFDictionaryGetValue(query, kSecAttrAccount);
1438
1439 // Check autofill enabled status
1440 if (!swca_autofill_enabled(clientAuditToken)) {
1441 SecError(errSecBadReq, error, CFSTR("Autofill is not enabled in Safari settings"));
1442 goto cleanup;
1443 }
1444
1445 // Check fqdn; if NULL, add domains from caller's entitlement.
1446 if (fqdn) {
1447 CFArrayAppendValue(fqdns, fqdn);
1448 }
1449 else if (domains) {
1450 CFIndex idx, count = CFArrayGetCount(domains);
1451 for (idx=0; idx < count; idx++) {
1452 CFStringRef str = (CFStringRef) CFArrayGetValueAtIndex(domains, idx);
1453 // Parse the entry for our service label prefix
1454 if (str && CFStringHasPrefix(str, kSecSharedWebCredentialsService)) {
1455 CFIndex prefix_len = CFStringGetLength(kSecSharedWebCredentialsService)+1;
1456 CFIndex substr_len = CFStringGetLength(str) - prefix_len;
1457 CFRange range = { prefix_len, substr_len };
1458 fqdn = CFStringCreateWithSubstring(kCFAllocatorDefault, str, range);
1459 if (fqdn) {
1460 CFArrayAppendValue(fqdns, fqdn);
1461 CFRelease(fqdn);
1462 }
1463 }
1464 }
1465 }
1466 count = CFArrayGetCount(fqdns);
1467 if (count < 1) {
1468 SecError(errSecParam, error, CFSTR("No domain provided"));
1469 goto cleanup;
1470 }
1471
1472 // Aggregate search results for each domain
1473 for (idx = 0; idx < count; idx++) {
1474 CFMutableArrayRef items = NULL;
1475 CFMutableDictionaryRef attrs = NULL;
1476 fqdn = (CFStringRef) CFArrayGetValueAtIndex(fqdns, idx);
1477 CFRetainSafe(fqdn);
1478 port = -1;
1479
1480 // Parse the fqdn for a possible port specifier.
1481 if (fqdn) {
1482 CFStringRef urlStr = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@%@"), kSecSharedCredentialUrlScheme, fqdn);
1483 if (urlStr) {
1484 CFURLRef url = CFURLCreateWithString(kCFAllocatorDefault, urlStr, nil);
1485 if (url) {
1486 CFStringRef hostname = CFURLCopyHostName(url);
1487 if (hostname) {
1488 CFReleaseSafe(fqdn);
1489 fqdn = hostname;
1490 port = CFURLGetPortNumber(url);
1491 }
1492 CFReleaseSafe(url);
1493 }
1494 CFReleaseSafe(urlStr);
1495 }
1496 }
1497
1498 #if TARGET_IPHONE_SIMULATOR
1499 secerror("app/site association entitlements not checked in Simulator");
1500 #else
1501 OSStatus status = errSecMissingEntitlement;
1502 if (!appID) {
1503 SecError(status, error, CFSTR("Missing application-identifier entitlement"));
1504 CFReleaseSafe(fqdn);
1505 goto cleanup;
1506 }
1507 // validate that fqdn is part of caller's entitlement
1508 if (_SecEntitlementContainsDomainForService(domains, fqdn, kSecSharedWebCredentialsService)) {
1509 status = errSecSuccess;
1510 }
1511 if (errSecSuccess != status) {
1512 CFStringRef msg = CFStringCreateWithFormat(kCFAllocatorDefault, NULL,
1513 CFSTR("%@ not found in %@ entitlement"), fqdn, kSecEntitlementAssociatedDomains);
1514 if (!msg) {
1515 msg = CFRetain(CFSTR("Requested domain not found in entitlement"));
1516 }
1517 SecError(status, error, CFSTR("%@"), msg);
1518 CFReleaseSafe(msg);
1519 CFReleaseSafe(fqdn);
1520 goto cleanup;
1521 }
1522 #endif
1523
1524 attrs = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1525 if (!attrs) {
1526 SecError(errSecAllocate, error, CFSTR("Unable to create query dictionary"));
1527 CFReleaseSafe(fqdn);
1528 goto cleanup;
1529 }
1530 CFDictionaryAddValue(attrs, kSecClass, kSecClassInternetPassword);
1531 CFDictionaryAddValue(attrs, kSecAttrAccessGroup, kSecSafariAccessGroup);
1532 CFDictionaryAddValue(attrs, kSecAttrAuthenticationType, kSecAttrAuthenticationTypeHTMLForm);
1533 CFDictionaryAddValue(attrs, kSecAttrServer, fqdn);
1534 if (account) {
1535 CFDictionaryAddValue(attrs, kSecAttrAccount, account);
1536 }
1537 if (port < -1 || port > 0) {
1538 SInt16 portValueShort = (port & 0xFFFF);
1539 CFNumberRef portNumber = CFNumberCreate(NULL, kCFNumberSInt16Type, &portValueShort);
1540 CFDictionaryAddValue(attrs, kSecAttrPort, portNumber);
1541 CFReleaseSafe(portNumber);
1542 }
1543 CFDictionaryAddValue(attrs, kSecAttrSynchronizable, kCFBooleanTrue);
1544 CFDictionaryAddValue(attrs, kSecMatchLimit, kSecMatchLimitAll);
1545 CFDictionaryAddValue(attrs, kSecReturnAttributes, kCFBooleanTrue);
1546 CFDictionaryAddValue(attrs, kSecReturnData, kCFBooleanTrue);
1547
1548 ok = _SecItemCopyMatching(attrs, accessGroups, (CFTypeRef*)&items, error);
1549 if (count > 1) {
1550 // ignore interim error since we have multiple domains to search
1551 CFReleaseNull(*error);
1552 }
1553 if (ok && items && CFGetTypeID(items) == CFArrayGetTypeID()) {
1554 #if TARGET_IPHONE_SIMULATOR
1555 secerror("Ignoring app/site approval state in the Simulator.");
1556 bool approved = true;
1557 #else
1558 // get approval status for this app/domain pair
1559 SWCFlags flags = _SecAppDomainApprovalStatus(appID, fqdn, error);
1560 if (count > 1) {
1561 // ignore interim error since we have multiple domains to check
1562 CFReleaseNull(*error);
1563 }
1564 bool approved = (flags & kSWCFlag_SiteApproved);
1565 #endif
1566 if (approved) {
1567 CFArrayAppendArray(foundItems, items, CFRangeMake(0, CFArrayGetCount(items)));
1568 }
1569 }
1570 CFReleaseSafe(items);
1571 CFReleaseSafe(attrs);
1572 CFReleaseSafe(fqdn);
1573 }
1574
1575 // If matching credentials are found, the credentials provided to the completionHandler
1576 // will be a CFArrayRef containing CFDictionaryRef entries. Each dictionary entry will
1577 // contain the following pairs (see Security/SecItem.h):
1578 // key: kSecAttrServer value: CFStringRef (the website)
1579 // key: kSecAttrAccount value: CFStringRef (the account)
1580 // key: kSecSharedPassword value: CFStringRef (the password)
1581 // Optional keys:
1582 // key: kSecAttrPort value: CFNumberRef (the port number, if non-standard for https)
1583
1584 count = CFArrayGetCount(foundItems);
1585 for (idx = 0; idx < count; idx++) {
1586 CFDictionaryRef dict = (CFDictionaryRef) CFArrayGetValueAtIndex(foundItems, idx);
1587 CFMutableDictionaryRef newdict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1588 if (newdict && dict && CFGetTypeID(dict) == CFDictionaryGetTypeID()) {
1589 CFStringRef srvr = CFDictionaryGetValue(dict, kSecAttrServer);
1590 CFStringRef acct = CFDictionaryGetValue(dict, kSecAttrAccount);
1591 CFNumberRef pnum = CFDictionaryGetValue(dict, kSecAttrPort);
1592 CFStringRef icmt = CFDictionaryGetValue(dict, kSecAttrComment);
1593 CFDataRef data = CFDictionaryGetValue(dict, kSecValueData);
1594 if (srvr) {
1595 CFDictionaryAddValue(newdict, kSecAttrServer, srvr);
1596 }
1597 if (acct) {
1598 CFDictionaryAddValue(newdict, kSecAttrAccount, acct);
1599 }
1600 if (pnum) {
1601 SInt16 pval = -1;
1602 if (CFNumberGetValue(pnum, kCFNumberSInt16Type, &pval) &&
1603 (pval < -1 || pval > 0)) {
1604 CFDictionaryAddValue(newdict, kSecAttrPort, pnum);
1605 }
1606 }
1607 if (data) {
1608 CFStringRef password = CFStringCreateFromExternalRepresentation(kCFAllocatorDefault, data, kCFStringEncodingUTF8);
1609 if (password) {
1610 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1611 CFDictionaryAddValue(newdict, kSecSharedPassword, password);
1612 #else
1613 CFDictionaryAddValue(newdict, CFSTR("spwd"), password);
1614 #endif
1615 CFReleaseSafe(password);
1616 }
1617 }
1618 if (icmt && CFEqual(icmt, kSecSafariDefaultComment)) {
1619 CFArrayInsertValueAtIndex(credentials, 0, newdict);
1620 } else {
1621 CFArrayAppendValue(credentials, newdict);
1622 }
1623 }
1624 CFReleaseSafe(newdict);
1625 }
1626
1627 if (count) {
1628
1629 ok = false;
1630
1631 // create a new array of dictionaries (without the actual password) for picker UI
1632 count = CFArrayGetCount(credentials);
1633 CFMutableArrayRef items = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
1634 for (idx = 0; idx < count; idx++) {
1635 CFDictionaryRef dict = (CFDictionaryRef) CFArrayGetValueAtIndex(credentials, idx);
1636 CFMutableDictionaryRef newdict = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, dict);
1637 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1638 CFDictionaryRemoveValue(newdict, kSecSharedPassword);
1639 #else
1640 CFDictionaryRemoveValue(newdict, CFSTR("spwd"));
1641 #endif
1642 CFArrayAppendValue(items, newdict);
1643 CFReleaseSafe(newdict);
1644 }
1645
1646 // prompt user to select one of the dictionary items
1647 CFDictionaryRef selected = swca_copy_selected_dictionary(swca_select_request_id,
1648 clientAuditToken, items, error);
1649 if (selected) {
1650 // find the matching item in our credentials array
1651 CFStringRef srvr = CFDictionaryGetValue(selected, kSecAttrServer);
1652 CFStringRef acct = CFDictionaryGetValue(selected, kSecAttrAccount);
1653 CFNumberRef pnum = CFDictionaryGetValue(selected, kSecAttrPort);
1654 for (idx = 0; idx < count; idx++) {
1655 CFDictionaryRef dict = (CFDictionaryRef) CFArrayGetValueAtIndex(credentials, idx);
1656 CFStringRef srvr1 = CFDictionaryGetValue(dict, kSecAttrServer);
1657 CFStringRef acct1 = CFDictionaryGetValue(dict, kSecAttrAccount);
1658 CFNumberRef pnum1 = CFDictionaryGetValue(dict, kSecAttrPort);
1659
1660 if (!srvr || !srvr1 || !CFEqual(srvr, srvr1)) continue;
1661 if (!acct || !acct1 || !CFEqual(acct, acct1)) continue;
1662 if ((pnum && pnum1) && !CFEqual(pnum, pnum1)) continue;
1663
1664 // we have a match!
1665 CFReleaseSafe(selected);
1666 CFRetainSafe(dict);
1667 selected = dict;
1668 ok = true;
1669 break;
1670 }
1671 }
1672 CFReleaseSafe(items);
1673 CFArrayRemoveAllValues(credentials);
1674 if (selected && ok) {
1675 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1676 fqdn = CFDictionaryGetValue(selected, kSecAttrServer);
1677 #endif
1678 CFArrayAppendValue(credentials, selected);
1679 }
1680
1681 #if 0
1682 // confirm the access
1683 ok = swca_confirm_operation(swca_copy_request_id, clientAuditToken, query, error,
1684 ^void (CFStringRef fqdn) { _SecAddNegativeWebCredential(fqdn, appID, false); });
1685 #endif
1686 if (ok) {
1687 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
1688 // register confirmation with database
1689 OSStatus status = _SecSWCEnsuredInitialized();
1690 if (status) {
1691 SecError(status, error, CFSTR("SWC initialize failed"));
1692 ok = false;
1693 CFReleaseSafe(selected);
1694 goto cleanup;
1695 }
1696 CFRetainSafe(appID);
1697 CFRetainSafe(fqdn);
1698 if (0 != sSWCSetServiceFlags_f(kSecSharedWebCredentialsService,
1699 appID, fqdn, kSWCFlag_ExternalMask, kSWCFlag_UserApproved,
1700 ^void(OSStatus inStatus, SWCFlags inNewFlags){
1701 CFReleaseSafe(appID);
1702 CFReleaseSafe(fqdn);
1703 }))
1704 {
1705 // we didn't queue the block
1706 CFReleaseSafe(appID);
1707 CFReleaseSafe(fqdn);
1708 }
1709 #endif
1710 }
1711 CFReleaseSafe(selected);
1712 }
1713 else if (NULL == *error) {
1714 // found no items, and we haven't already filled in the error
1715 SecError(errSecItemNotFound, error, CFSTR("no matching items found"));
1716 }
1717
1718 cleanup:
1719 if (!ok) {
1720 CFArrayRemoveAllValues(credentials);
1721 }
1722 CFReleaseSafe(foundItems);
1723 *result = credentials;
1724 CFReleaseSafe(accessGroups);
1725 CFReleaseSafe(fqdns);
1726 #if 0 /* debugging */
1727 secerror("result=%s, copied items %@, error=%@", (ok) ? "true" : "false", *result, *error);
1728 #endif
1729 return ok;
1730 }
1731
1732 // MARK: -
1733 // MARK: Keychain backup
1734
1735 CF_RETURNS_RETAINED CFDataRef
1736 _SecServerKeychainBackup(CFDataRef keybag, CFDataRef passcode, CFErrorRef *error) {
1737 CFDataRef backup;
1738 SecDbConnectionRef dbt = SecDbConnectionAquire(kc_dbhandle(), false, error);
1739
1740 if (!dbt)
1741 return NULL;
1742
1743 if (keybag == NULL && passcode == NULL) {
1744 #if USE_KEYSTORE
1745 backup = SecServerExportBackupableKeychain(dbt, KEYBAG_DEVICE, backup_keybag_handle, error);
1746 #else /* !USE_KEYSTORE */
1747 SecError(errSecParam, error, CFSTR("Why are you doing this?"));
1748 backup = NULL;
1749 #endif /* USE_KEYSTORE */
1750 } else {
1751 backup = SecServerKeychainBackup(dbt, keybag, passcode, error);
1752 }
1753
1754 SecDbConnectionRelease(dbt);
1755
1756 return backup;
1757 }
1758
1759 bool
1760 _SecServerKeychainRestore(CFDataRef backup, CFDataRef keybag, CFDataRef passcode, CFErrorRef *error) {
1761 if (backup == NULL || keybag == NULL)
1762 return SecError(errSecParam, error, CFSTR("backup or keybag missing"));
1763
1764 __block bool ok = true;
1765 ok &= SecDbPerformWrite(kc_dbhandle(), error, ^(SecDbConnectionRef dbconn) {
1766 ok = SecServerKeychainRestore(dbconn, backup, keybag, passcode, error);
1767 });
1768
1769 if (ok) {
1770 SecKeychainChanged(true);
1771 }
1772
1773 return ok;
1774 }
1775
1776
1777 // MARK: -
1778 // MARK: SecItemDataSource
1779
1780 // Make sure to call this before any writes to the keychain, so that we fire
1781 // up the engines to monitor manifest changes.
1782 SOSDataSourceFactoryRef SecItemDataSourceFactoryGetDefault(void) {
1783 return SecItemDataSourceFactoryGetShared(kc_dbhandle());
1784 }
1785
1786 /* AUDIT[securityd]:
1787 args_in (ok) is a caller provided, CFDictionaryRef.
1788 */
1789
1790 CF_RETURNS_RETAINED CFArrayRef
1791 _SecServerKeychainSyncUpdateMessage(CFDictionaryRef updates, CFErrorRef *error) {
1792 // This never fails, trust us!
1793 return SOSCCHandleUpdateMessage(updates);
1794 }
1795
1796 //
1797 // Truthiness in the cloud backup/restore support.
1798 //
1799
1800 static CFDictionaryRef
1801 _SecServerCopyTruthInTheCloud(CFDataRef keybag, CFDataRef password,
1802 CFDictionaryRef backup, CFErrorRef *error)
1803 {
1804 SOSManifestRef mold = NULL, mnow = NULL, mdelete = NULL, madd = NULL;
1805 __block CFMutableDictionaryRef backup_new = NULL;
1806 keybag_handle_t bag_handle;
1807 if (!ks_open_keybag(keybag, password, &bag_handle, error))
1808 return backup_new;
1809
1810 // We need to have a datasource singleton for protection domain
1811 // kSecAttrAccessibleWhenUnlocked and keep a single shared engine
1812 // instance around which we create in the datasource constructor as well.
1813 SOSDataSourceFactoryRef dsf = SecItemDataSourceFactoryGetDefault();
1814 SOSDataSourceRef ds = SOSDataSourceFactoryCreateDataSource(dsf, kSecAttrAccessibleWhenUnlocked, error);
1815 if (ds) {
1816 backup_new = backup ? CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, backup) : CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1817 mold = SOSCreateManifestWithBackup(backup, error);
1818 SOSEngineRef engine = SOSDataSourceGetSharedEngine(ds, error);
1819 mnow = SOSEngineCopyManifest(engine, NULL);
1820 if (!mnow) {
1821 mnow = SOSDataSourceCopyManifestWithViewNameSet(ds, SOSViewsGetV0ViewSet(), error);
1822 }
1823 if (!mnow) {
1824 CFReleaseNull(backup_new);
1825 secerror("failed to obtain manifest for keychain: %@", error ? *error : NULL);
1826 } else {
1827 SOSManifestDiff(mold, mnow, &mdelete, &madd, error);
1828 }
1829
1830 // Delete everything from the new_backup that is no longer in the datasource according to the datasources manifest.
1831 SOSManifestForEach(mdelete, ^(CFDataRef digest_data, bool *stop) {
1832 CFStringRef deleted_item_key = CFDataCopyHexString(digest_data);
1833 CFDictionaryRemoveValue(backup_new, deleted_item_key);
1834 CFRelease(deleted_item_key);
1835 });
1836
1837 CFMutableArrayRef changes = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
1838 SOSDataSourceForEachObject(ds, madd, error, ^void(CFDataRef digest, SOSObjectRef object, bool *stop) {
1839 CFErrorRef localError = NULL;
1840 CFDataRef digest_data = NULL;
1841 CFTypeRef value = NULL;
1842 if (!object) {
1843 // Key in our manifest can't be found in db, remove it from our manifest
1844 SOSChangesAppendDelete(changes, digest);
1845 } else if (!(digest_data = SOSObjectCopyDigest(ds, object, &localError))
1846 || !(value = SOSObjectCopyBackup(ds, object, bag_handle, &localError))) {
1847 if (SecErrorGetOSStatus(localError) == errSecDecode) {
1848 // Ignore decode errors, pretend the objects aren't there
1849 CFRelease(localError);
1850 // Object undecodable, remove it from our manifest
1851 SOSChangesAppendDelete(changes, digest);
1852 } else {
1853 // Stop iterating and propagate out all other errors.
1854 *stop = true;
1855 *error = localError;
1856 CFReleaseNull(backup_new);
1857 }
1858 } else {
1859 // TODO: Should we skip tombstones here?
1860 CFStringRef key = CFDataCopyHexString(digest_data);
1861 CFDictionarySetValue(backup_new, key, value);
1862 CFReleaseSafe(key);
1863 }
1864 CFReleaseSafe(digest_data);
1865 CFReleaseSafe(value);
1866 }) || CFReleaseNull(backup_new);
1867
1868 if (CFArrayGetCount(changes)) {
1869 if (!SOSEngineUpdateChanges(engine, kSOSDataSourceSOSTransaction, changes, error)) {
1870 CFReleaseNull(backup_new);
1871 }
1872 }
1873 CFReleaseSafe(changes);
1874
1875 SOSDataSourceRelease(ds, error) || CFReleaseNull(backup_new);
1876 }
1877
1878 CFReleaseSafe(mold);
1879 CFReleaseSafe(mnow);
1880 CFReleaseSafe(madd);
1881 CFReleaseSafe(mdelete);
1882 ks_close_keybag(bag_handle, error) || CFReleaseNull(backup_new);
1883
1884 return backup_new;
1885 }
1886
1887 static bool
1888 _SecServerRestoreTruthInTheCloud(CFDataRef keybag, CFDataRef password, CFDictionaryRef backup_in, CFErrorRef *error) {
1889 __block bool ok = true;
1890 keybag_handle_t bag_handle;
1891 if (!ks_open_keybag(keybag, password, &bag_handle, error))
1892 return false;
1893
1894 SOSManifestRef mbackup = SOSCreateManifestWithBackup(backup_in, error);
1895 if (mbackup) {
1896 SOSDataSourceFactoryRef dsf = SecItemDataSourceFactoryGetDefault();
1897 SOSDataSourceRef ds = SOSDataSourceFactoryCreateDataSource(dsf, kSecAttrAccessibleWhenUnlocked, error);
1898 ok &= ds && SOSDataSourceWith(ds, error, ^(SOSTransactionRef txn, bool *commit) {
1899 SOSManifestRef mnow = SOSDataSourceCopyManifestWithViewNameSet(ds, SOSViewsGetV0BackupViewSet(), error);
1900 SOSManifestRef mdelete = NULL, madd = NULL;
1901 SOSManifestDiff(mnow, mbackup, &mdelete, &madd, error);
1902
1903 // Don't delete everything in datasource not in backup.
1904
1905 // Add items from the backup
1906 SOSManifestForEach(madd, ^void(CFDataRef e, bool *stop) {
1907 CFDictionaryRef item = NULL;
1908 CFStringRef sha1 = CFDataCopyHexString(e);
1909 if (sha1) {
1910 item = CFDictionaryGetValue(backup_in, sha1);
1911 CFRelease(sha1);
1912 }
1913 if (item) {
1914 CFErrorRef localError = NULL;
1915
1916 if (!SOSObjectRestoreObject(ds, txn, bag_handle, item, &localError)) {
1917 OSStatus status = SecErrorGetOSStatus(localError);
1918 if (status == errSecDuplicateItem) {
1919 // Log and ignore duplicate item errors during restore
1920 secnotice("titc", "restore %@ not replacing existing item", item);
1921 } else if (status == errSecDecode) {
1922 // Log and ignore corrupted item errors during restore
1923 secnotice("titc", "restore %@ skipping corrupted item %@", item, localError);
1924 } else {
1925 if (status == errSecInteractionNotAllowed)
1926 *stop = true;
1927 // Propagate the first other error upwards (causing the restore to fail).
1928 secerror("restore %@ failed %@", item, localError);
1929 ok = false;
1930 if (error && !*error) {
1931 *error = localError;
1932 localError = NULL;
1933 }
1934 }
1935 CFReleaseSafe(localError);
1936 }
1937 }
1938 });
1939 ok &= SOSDataSourceRelease(ds, error);
1940 CFReleaseNull(mdelete);
1941 CFReleaseNull(madd);
1942 CFReleaseNull(mnow);
1943 });
1944 CFRelease(mbackup);
1945 }
1946
1947 ok &= ks_close_keybag(bag_handle, error);
1948
1949 return ok;
1950 }
1951
1952
1953 CF_RETURNS_RETAINED CFDictionaryRef
1954 _SecServerBackupSyncable(CFDictionaryRef backup, CFDataRef keybag, CFDataRef password, CFErrorRef *error) {
1955 require_action_quiet(isData(keybag), errOut, SecError(errSecParam, error, CFSTR("keybag %@ not a data"), keybag));
1956 require_action_quiet(!backup || isDictionary(backup), errOut, SecError(errSecParam, error, CFSTR("backup %@ not a dictionary"), backup));
1957 require_action_quiet(!password || isData(password), errOut, SecError(errSecParam, error, CFSTR("password %@ not a data"), password));
1958
1959 return _SecServerCopyTruthInTheCloud(keybag, password, backup, error);
1960
1961 errOut:
1962 return NULL;
1963 }
1964
1965 bool
1966 _SecServerRestoreSyncable(CFDictionaryRef backup, CFDataRef keybag, CFDataRef password, CFErrorRef *error) {
1967 bool ok;
1968 require_action_quiet(isData(keybag), errOut, ok = SecError(errSecParam, error, CFSTR("keybag %@ not a data"), keybag));
1969 require_action_quiet(isDictionary(backup), errOut, ok = SecError(errSecParam, error, CFSTR("backup %@ not a dictionary"), backup));
1970 if (password) {
1971 require_action_quiet(isData(password), errOut, ok = SecError(errSecParam, error, CFSTR("password not a data")));
1972 }
1973
1974 ok = _SecServerRestoreTruthInTheCloud(keybag, password, backup, error);
1975
1976 errOut:
1977 return ok;
1978 }
1979
1980 bool _SecServerRollKeys(bool force, CFErrorRef *error) {
1981 #if USE_KEYSTORE
1982 uint32_t keystore_generation_status = 0;
1983 if (aks_generation(KEYBAG_DEVICE, generation_noop, &keystore_generation_status))
1984 return false;
1985 uint32_t current_generation = keystore_generation_status & generation_current;
1986
1987 return kc_with_dbt(true, error, ^(SecDbConnectionRef dbt) {
1988 bool up_to_date = s3dl_dbt_keys_current(dbt, current_generation, NULL);
1989
1990 if (force && !up_to_date) {
1991 up_to_date = s3dl_dbt_update_keys(dbt, error);
1992 if (up_to_date) {
1993 secerror("Completed roll keys.");
1994 up_to_date = s3dl_dbt_keys_current(dbt, current_generation, NULL);
1995 }
1996 if (!up_to_date)
1997 secerror("Failed to roll keys.");
1998 }
1999 return up_to_date;
2000 });
2001 #else
2002 return true;
2003 #endif
2004 }
2005
2006 bool
2007 _SecServerGetKeyStats(const SecDbClass *qclass,
2008 struct _SecServerKeyStats *stats)
2009 {
2010 __block CFErrorRef error = NULL;
2011 bool res = false;
2012
2013 Query *q = query_create(qclass, NULL, &error);
2014 require(q, fail);
2015
2016 q->q_return_type = kSecReturnDataMask | kSecReturnAttributesMask;
2017 q->q_limit = kSecMatchUnlimited;
2018 q->q_keybag = KEYBAG_DEVICE;
2019 query_add_or_attribute(kSecAttrAccessible, kSecAttrAccessibleWhenUnlocked, q);
2020 query_add_or_attribute(kSecAttrAccessible, kSecAttrAccessibleAfterFirstUnlock, q);
2021 query_add_or_attribute(kSecAttrAccessible, kSecAttrAccessibleAlways, q);
2022 query_add_or_attribute(kSecAttrAccessible, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, q);
2023 query_add_or_attribute(kSecAttrAccessible, kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, q);
2024 query_add_or_attribute(kSecAttrAccessible, kSecAttrAccessibleAlwaysThisDeviceOnly, q);
2025 query_add_attribute(kSecAttrTombstone, kCFBooleanFalse, q);
2026
2027 kc_with_dbt(false, &error, ^(SecDbConnectionRef dbconn) {
2028 CFErrorRef error2 = NULL;
2029 __block CFIndex totalSize = 0;
2030 stats->maxDataSize = 0;
2031
2032 SecDbItemSelect(q, dbconn, &error2, NULL, ^bool(const SecDbAttr *attr) {
2033 return CFDictionaryContainsKey(q->q_item, attr->name);
2034 }, NULL, NULL, ^(SecDbItemRef item, bool *stop) {
2035 CFErrorRef error3 = NULL;
2036 CFDataRef data = SecDbItemGetValue(item, &v6v_Data, &error3);
2037 if (isData(data)) {
2038 CFIndex size = CFDataGetLength(data);
2039 if (size > stats->maxDataSize)
2040 stats->maxDataSize = size;
2041 totalSize += size;
2042 stats->items++;
2043 }
2044 CFReleaseNull(error3);
2045 });
2046 CFReleaseNull(error2);
2047 if (stats->items)
2048 stats->averageSize = totalSize / stats->items;
2049
2050 return (bool)true;
2051 });
2052
2053
2054 res = true;
2055
2056 fail:
2057 CFReleaseNull(error);
2058 if (q)
2059 query_destroy(q, NULL);
2060 return res;
2061 }