]> git.saurik.com Git - apple/security.git/blob - OSX/sec/SharedWebCredential/swcagent.m
071eb04107105b572f10bcbb227940c01ab7d2f2
[apple/security.git] / OSX / sec / SharedWebCredential / swcagent.m
1 /*
2 * Copyright (c) 2014-2016 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 #include <mach/mach.h>
25 #include <mach/message.h>
26
27 #include <stdlib.h>
28 #include <sys/queue.h>
29 #include <libproc.h>
30
31 #include <Security/SecInternal.h>
32 #include <Security/SecBasePriv.h>
33 #include <Security/SecItemPriv.h> /* for SecErrorGetOSStatus */
34 #include <CoreFoundation/CoreFoundation.h>
35 #include <CoreFoundation/CFPriv.h>
36
37 #include <Foundation/Foundation.h>
38
39 #if TARGET_OS_IPHONE
40 #include <CoreFoundation/CFUserNotification.h>
41 #else
42 #include <CoreFoundation/CFUserNotificationPriv.h>
43 #endif
44
45 #if TARGET_OS_IPHONE
46 #include <SpringBoardServices/SpringBoardServices.h>
47 #include <MobileCoreServices/LSApplicationProxy.h>
48 #endif
49
50 #if TARGET_OS_IPHONE && !TARGET_OS_NANO
51 #include <dlfcn.h>
52 #include <WebUI/WBUAutoFillData.h>
53
54 typedef WBSAutoFillDataClasses (*WBUAutoFillGetEnabledDataClasses_f)(void);
55 #endif
56
57 #include <syslog.h>
58 #include <bsm/libbsm.h>
59 #include <utilities/SecIOFormat.h>
60 #include <utilities/debugging.h>
61
62 #include <ipc/securityd_client.h>
63 #include "swcagent_client.h"
64
65 #include <securityd/SecItemServer.h>
66 #include <securityd/SecTrustServer.h>
67 #include <securityd/SecTrustStoreServer.h>
68 #include <securityd/spi.h>
69 #include <Security/SecTask.h>
70
71 #include <utilities/SecCFWrappers.h>
72 #include <utilities/SecCFError.h>
73 #include <utilities/SecXPCError.h>
74
75 #include <Security/SecEntitlements.h>
76
77 #include <Security/SecuritydXPC.h>
78
79 #include <xpc/xpc.h>
80 #include <xpc/private.h>
81 #include <xpc/connection_private.h>
82 #include <AssertMacros.h>
83
84 #import <LocalAuthentication/LocalAuthentication.h>
85
86 static NSString *swca_string_table = @"SharedWebCredentials";
87
88 static bool SecTaskGetBooleanValueForEntitlement(SecTaskRef task, CFStringRef entitlement)
89 {
90 CFStringRef canModify = (CFStringRef)SecTaskCopyValueForEntitlement(task, entitlement, NULL);
91 if (!canModify)
92 return false;
93 CFTypeID canModifyType = CFGetTypeID(canModify);
94 bool ok = (CFBooleanGetTypeID() == canModifyType) && CFBooleanGetValue((CFBooleanRef)canModify);
95 CFRelease(canModify);
96 return ok;
97 }
98
99 static CFArrayRef SecTaskCopyArrayOfStringsForEntitlement(SecTaskRef task, CFStringRef entitlement)
100 {
101 CFArrayRef value = (CFArrayRef)SecTaskCopyValueForEntitlement(task,
102 entitlement, NULL);
103 if (value) {
104 if (CFGetTypeID(value) == CFArrayGetTypeID()) {
105 CFIndex ix, count = CFArrayGetCount(value);
106 for (ix = 0; ix < count; ++ix) {
107 CFStringRef string = (CFStringRef)CFArrayGetValueAtIndex(value, ix);
108 if (CFGetTypeID(string) != CFStringGetTypeID()) {
109 CFRelease(value);
110 value = NULL;
111 break;
112 }
113 }
114 } else {
115 CFRelease(value);
116 value = NULL;
117 }
118 }
119
120 return value;
121 }
122
123 /* Identify a client */
124 enum {
125 CLIENT_TYPE_BUNDLE_IDENTIFIER,
126 CLIENT_TYPE_EXECUTABLE_PATH,
127 };
128
129 @interface Client : NSObject
130 @property int client_type;
131 @property(retain) NSString *client;
132 @property(retain) NSString *client_name;
133 @property(retain) NSString *path;
134 @property(retain) NSBundle *bundle;
135 @end
136
137 @implementation Client
138 @end
139
140 static Client *identify_client(pid_t pid)
141 {
142 Client *client = [[Client alloc] init];
143 if (!client)
144 return nil;
145
146 char path_buf[PROC_PIDPATHINFO_SIZE] = "";
147 NSURL *path_url;
148
149 #if TARGET_OS_IPHONE
150 if (proc_pidpath(pid, path_buf, sizeof(path_buf)) <= 0) {
151 secnotice("swcagent", "Refusing client without path (pid %d)", pid);
152 return nil;
153 }
154 #else
155 size_t path_len = sizeof(path_buf);
156 if (responsibility_get_responsible_for_pid(pid, NULL, NULL, &path_len, path_buf) != 0) {
157 secnotice("swcagent", "Refusing client without path (pid %d)", pid);
158 [client release];
159 return nil;
160 }
161 #endif
162 path_buf[sizeof(path_buf) - 1] = '\0';
163
164 if (!(client.path = [NSString stringWithUTF8String:path_buf]) ||
165 !(path_url = [NSURL fileURLWithPath:client.path])) {
166 secnotice("swcagent", "Refusing client without path (pid %d)", pid);
167 return nil;
168 }
169
170 NSURL *bundle_url;
171 if ((bundle_url = CFBridgingRelease(_CFBundleCopyBundleURLForExecutableURL((__bridge CFURLRef)path_url))) &&
172 (client.bundle = [NSBundle bundleWithURL:bundle_url]) &&
173 (client.client = [client.bundle bundleIdentifier])) {
174 client.client_type = CLIENT_TYPE_BUNDLE_IDENTIFIER;
175 CFStringRef client_name_cf = NULL;
176 #if TARGET_OS_IPHONE
177 client.client_name = [[LSApplicationProxy applicationProxyForIdentifier:client.client] localizedNameForContext:nil];
178 #else
179 if (!LSCopyDisplayNameForURL((__bridge CFURLRef)bundle_url, &client_name_cf))
180 client.client_name = (__bridge_transfer NSString *)client_name_cf;
181 #endif
182 if (client_name_cf)
183 CFRelease(client_name_cf);
184 } else {
185 #if TARGET_OS_IPHONE
186 secnotice("swcagent", "Refusing client without bundle identifier (%s)", path_buf);
187 return nil;
188 #else
189 client.client_type = CLIENT_TYPE_EXECUTABLE_PATH;
190 CFBooleanRef is_app = NULL;
191 CFStringRef client_name_cf;
192 if (bundle_url &&
193 CFURLCopyResourcePropertyForKey((__bridge CFURLRef)bundle_url, kCFURLIsApplicationKey, &is_app, NULL) &&
194 is_app == kCFBooleanTrue) {
195 if ((client.client = [bundle_url path]) &&
196 !LSCopyDisplayNameForURL((__bridge CFURLRef)bundle_url, &client_name_cf))
197 client.client_name = (__bridge_transfer NSString *)client_name_cf;
198 } else {
199 client.client = client.path;
200 if (!LSCopyDisplayNameForURL((__bridge CFURLRef)path_url, &client_name_cf))
201 client.client_name = (__bridge_transfer NSString *)client_name_cf;
202 }
203 if (is_app)
204 CFRelease(is_app);
205 #endif
206 }
207
208 return client;
209 }
210
211 struct __SecTask {
212 CFRuntimeBase base;
213
214 audit_token_t token;
215
216 /* Track whether we've loaded entitlements independently since after the
217 * load, entitlements may legitimately be NULL */
218 Boolean entitlementsLoaded;
219 CFDictionaryRef entitlements;
220
221 /* for debugging only, shown by debugDescription */
222 int lastFailure;
223 };
224
225 static Client* SecTaskCopyClient(SecTaskRef task)
226 {
227 // SecTaskCopyDebugDescription is not sufficient to get the localized client name
228 pid_t pid;
229 audit_token_to_au32(task->token, NULL, NULL, NULL, NULL, NULL, &pid, NULL, NULL);
230 Client *client = identify_client(pid);
231 return client;
232 }
233
234 static CFArrayRef SecTaskCopyAccessGroups(SecTaskRef task) {
235 return SecTaskCopyArrayOfStringsForEntitlement(task, kSecEntitlementAssociatedDomains);
236 }
237
238 // Local function declarations
239
240 static CFArrayRef gActiveArray = NULL;
241 static CFDictionaryRef gActiveItem = NULL;
242
243
244 static CFStringRef SWCAGetOperationDescription(enum SWCAXPCOperation op)
245 {
246 switch (op) {
247 case swca_add_request_id:
248 return CFSTR("swc add");
249 case swca_update_request_id:
250 return CFSTR("swc update");
251 case swca_delete_request_id:
252 return CFSTR("swc delete");
253 case swca_copy_request_id:
254 return CFSTR("swc copy");
255 case swca_select_request_id:
256 return CFSTR("swc select");
257 case swca_copy_pairs_request_id:
258 return CFSTR("swc copy pairs");
259 case swca_set_selection_request_id:
260 return CFSTR("swc set selection");
261 case swca_enabled_request_id:
262 return CFSTR("swc enabled");
263 default:
264 return CFSTR("Unknown xpc operation");
265 }
266 }
267
268 #if !TARGET_IPHONE_SIMULATOR && TARGET_OS_IPHONE && !TARGET_OS_NANO
269 static dispatch_once_t sWBUInitializeOnce = 0;
270 static void * sWBULibrary = NULL;
271 static WBUAutoFillGetEnabledDataClasses_f sWBUAutoFillGetEnabledDataClasses_f = NULL;
272
273 static OSStatus _SecWBUEnsuredInitialized(void);
274
275 static OSStatus _SecWBUEnsuredInitialized(void)
276 {
277 __block OSStatus status = errSecNotAvailable;
278
279 dispatch_once(&sWBUInitializeOnce, ^{
280 sWBULibrary = dlopen("/System/Library/PrivateFrameworks/WebUI.framework/WebUI", RTLD_LAZY | RTLD_LOCAL);
281 assert(sWBULibrary);
282 if (sWBULibrary) {
283 sWBUAutoFillGetEnabledDataClasses_f = (WBUAutoFillGetEnabledDataClasses_f)(uintptr_t) dlsym(sWBULibrary, "WBUAutoFillGetEnabledDataClasses");
284 }
285 });
286
287 if (sWBUAutoFillGetEnabledDataClasses_f) {
288 status = noErr;
289 }
290 return status;
291 }
292 #endif
293
294 static bool SWCAIsAutofillEnabled(void)
295 {
296 #if TARGET_IPHONE_SIMULATOR
297 // Assume the setting's on in the simulator: <rdar://problem/17057358> WBUAutoFillGetEnabledDataClasses call failing in the Simulator
298 return true;
299 #elif TARGET_OS_IPHONE && !TARGET_OS_NANO
300 OSStatus status = _SecWBUEnsuredInitialized();
301 if (status) { return false; }
302 WBSAutoFillDataClasses autofill = sWBUAutoFillGetEnabledDataClasses_f();
303 return ((autofill & WBSAutoFillDataClassUsernamesAndPasswords) != 0);
304 #else
305 //%%% unsupported platform
306 return false;
307 #endif
308 }
309
310 static NSBundle* swca_get_security_bundle()
311 {
312 static NSBundle *security_bundle;
313 static dispatch_once_t onceToken;
314 dispatch_once(&onceToken, ^{
315 NSString *security_path = @"/System/Library/Frameworks/Security.framework";
316 #if TARGET_IPHONE_SIMULATOR
317 security_path = [NSString stringWithFormat:@"%s%@", getenv("IPHONE_SIMULATOR_ROOT"), security_path];
318 #endif
319 security_bundle = [NSBundle bundleWithPath:security_path];
320 });
321 return security_bundle;
322 }
323
324 static CFOptionFlags swca_handle_request(enum SWCAXPCOperation operation, Client* client, CFArrayRef domains)
325 {
326 CFUserNotificationRef notification = NULL;
327 NSMutableDictionary *notification_dictionary = NULL;
328 NSString *request_key;
329 NSString *request_format;
330 NSString *default_button_key;
331 NSString *alternate_button_key;
332 NSString *other_button_key;
333 NSString *info_message_key;
334 NSString *domain;
335 char *op = NULL;
336 CFOptionFlags response = 0 | kCFUserNotificationCancelResponse;
337 BOOL alert_sem_held = NO;
338
339 check_database:
340 /* If we have previously allowed this operation/domain for this client,
341 * check and don't prompt again.
342 */
343 ; /* %%% TBD */
344
345 /* Only display one alert at a time. */
346 static dispatch_semaphore_t alert_sem;
347 static dispatch_once_t alert_once;
348 dispatch_once(&alert_once, ^{
349 if (!(alert_sem = dispatch_semaphore_create(1)))
350 abort();
351 });
352 if (!alert_sem_held) {
353 alert_sem_held = YES;
354 if (dispatch_semaphore_wait(alert_sem, DISPATCH_TIME_NOW)) {
355 /* Wait for the active alert, then recheck the database in case both alerts are for the same client. */
356 //secnotice("swcagent", "Delaying prompt for pid %d", pid);
357 dispatch_semaphore_wait(alert_sem, DISPATCH_TIME_FOREVER);
358 goto check_database;
359 }
360 }
361
362 notification_dictionary = [NSMutableDictionary dictionary];
363 domain = nil;
364 if (1 == [(__bridge NSArray *)domains count]) {
365 domain = (NSString *)[(__bridge NSArray *)domains objectAtIndex:0];
366 }
367 switch (operation) {
368 case swca_add_request_id:
369 op = "ADD";
370 break;
371 case swca_update_request_id:
372 op = "UPDATE";
373 break;
374 case swca_delete_request_id:
375 op = "DELETE";
376 break;
377 case swca_copy_request_id:
378 op = (domain) ? "COPY" : "COPYALL";
379 break;
380 default:
381 op = "USE";
382 break;
383 }
384 if (!op) {
385 goto out;
386 }
387 request_key = [NSString stringWithFormat:@"SWC_REQUEST_%s", op];
388 request_format = NSLocalizedStringFromTableInBundle(request_key, swca_string_table, swca_get_security_bundle(), nil);
389 alternate_button_key = (op) ? [NSString stringWithFormat:@"SWC_ALLOW_%s", op] : nil;
390 default_button_key = @"SWC_NEVER";
391 other_button_key = @"SWC_DENY";
392 info_message_key = @"SWC_INFO_MESSAGE";
393
394 #pragma clang diagnostic push
395 #pragma clang diagnostic ignored "-Wformat-nonliteral"
396 notification_dictionary[(__bridge NSString *)kCFUserNotificationAlertHeaderKey] = [NSString stringWithFormat:request_format, client.client_name, domain];
397 #pragma clang diagnostic pop
398 notification_dictionary[(__bridge NSString *)kCFUserNotificationAlertMessageKey] = NSLocalizedStringFromTableInBundle(info_message_key, swca_string_table, swca_get_security_bundle(), nil);
399 notification_dictionary[(__bridge NSString *)kCFUserNotificationDefaultButtonTitleKey] = NSLocalizedStringFromTableInBundle(default_button_key, swca_string_table, swca_get_security_bundle(), nil);
400 notification_dictionary[(__bridge NSString *)kCFUserNotificationAlternateButtonTitleKey] = NSLocalizedStringFromTableInBundle(alternate_button_key, swca_string_table, swca_get_security_bundle(), nil);
401
402 if (other_button_key) {
403 // notification_dictionary[(__bridge NSString *)kCFUserNotificationOtherButtonTitleKey] = NSLocalizedStringFromTableInBundle(other_button_key, swc_table, security_bundle, nil);
404 }
405 notification_dictionary[(__bridge NSString *)kCFUserNotificationLocalizationURLKey] = [swca_get_security_bundle() bundleURL];
406 notification_dictionary[(__bridge NSString *)SBUserNotificationAllowedApplicationsKey] = client.client;
407
408 SInt32 error;
409 if (!(notification = CFUserNotificationCreate(NULL, 0, kCFUserNotificationStopAlertLevel | kCFUserNotificationNoDefaultButtonFlag, &error, (__bridge CFDictionaryRef)notification_dictionary)) ||
410 error)
411 goto out;
412 if (CFUserNotificationReceiveResponse(notification, 0, &response))
413 goto out;
414
415 out:
416 if (alert_sem_held)
417 dispatch_semaphore_signal(alert_sem);
418 if (notification)
419 CFRelease(notification);
420 return response;
421 }
422
423 static bool swca_process_response(CFOptionFlags response, CFTypeRef *result)
424 {
425 int32_t value = (int32_t)(response & 0x3);
426 CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &value);
427 *result = number;
428 return (NULL != number);
429 }
430
431 static bool swca_confirm_add(CFDictionaryRef attributes, Client* client, CFArrayRef accessGroups, CFTypeRef *result, CFErrorRef *error)
432 {
433 CFStringRef domain = (CFStringRef) CFDictionaryGetValue(attributes, kSecAttrServer);
434 CFArrayRef domains = CFArrayCreate(kCFAllocatorDefault, (const void **)&domain, 1, &kCFTypeArrayCallBacks);
435 CFOptionFlags response = swca_handle_request(swca_add_request_id, client, domains);
436 return swca_process_response(response, result);
437 }
438
439 static bool swca_confirm_copy(CFDictionaryRef attributes, Client* client, CFArrayRef accessGroups, CFTypeRef *result, CFErrorRef *error)
440 {
441 CFStringRef domain = (CFStringRef) CFDictionaryGetValue(attributes, kSecAttrServer);
442 CFArrayRef domains = CFArrayCreate(kCFAllocatorDefault, (const void **)&domain, 1, &kCFTypeArrayCallBacks);
443 CFOptionFlags response = swca_handle_request(swca_copy_request_id, client, domains);
444 return swca_process_response(response, result);
445 }
446
447 static bool swca_confirm_update(CFDictionaryRef attributes, Client* client, CFArrayRef accessGroups, CFTypeRef *result, CFErrorRef *error)
448 {
449 CFStringRef domain = (CFStringRef) CFDictionaryGetValue(attributes, kSecAttrServer);
450 CFArrayRef domains = CFArrayCreate(kCFAllocatorDefault, (const void **)&domain, 1, &kCFTypeArrayCallBacks);
451 CFOptionFlags response = swca_handle_request(swca_update_request_id, client, domains);
452 return swca_process_response(response, result);
453 }
454
455 static bool swca_confirm_delete(CFDictionaryRef attributes, Client* client, CFArrayRef accessGroups, CFTypeRef *result, CFErrorRef *error)
456 {
457 CFStringRef domain = (CFStringRef) CFDictionaryGetValue(attributes, kSecAttrServer);
458 CFArrayRef domains = CFArrayCreate(kCFAllocatorDefault, (const void **)&domain, 1, &kCFTypeArrayCallBacks);
459 CFOptionFlags response = swca_handle_request(swca_delete_request_id, client, domains);
460 return swca_process_response(response, result);
461 }
462
463 static bool swca_select_item(CFArrayRef items, Client* client, CFArrayRef accessGroups, CFTypeRef *result, CFErrorRef *error)
464 {
465 CFUserNotificationRef notification = NULL;
466 NSMutableDictionary *notification_dictionary = NULL;
467 NSString *request_title_format;
468 NSString *info_message_key;
469 NSString *default_button_key;
470 NSString *alternate_button_key;
471 CFOptionFlags response = 0 | kCFUserNotificationCancelResponse;
472 CFIndex item_count = (items) ? CFArrayGetCount(items) : (CFIndex) 0;
473 BOOL alert_sem_held = NO;
474
475 if (item_count < 1) {
476 return false;
477 }
478
479 entry:
480 ;
481 /* Only display one alert at a time. */
482 static dispatch_semaphore_t select_alert_sem;
483 static dispatch_once_t select_alert_once;
484 dispatch_once(&select_alert_once, ^{
485 if (!(select_alert_sem = dispatch_semaphore_create(1)))
486 abort();
487 });
488 if (!alert_sem_held) {
489 alert_sem_held = YES;
490 if (dispatch_semaphore_wait(select_alert_sem, DISPATCH_TIME_NOW)) {
491 /* Wait for the active alert */
492 dispatch_semaphore_wait(select_alert_sem, DISPATCH_TIME_FOREVER);
493 goto entry;
494 }
495 }
496
497 CFRetainSafe(items);
498 CFReleaseSafe(gActiveArray);
499 gActiveArray = items;
500 CFReleaseSafe(gActiveItem);
501 gActiveItem = NULL; // selection will be set by remote view controller
502 //gActiveItem = CFArrayGetValueAtIndex(items, 0);
503 //CFRetainSafe(gActiveItem);
504
505 notification_dictionary = [NSMutableDictionary dictionary];
506 request_title_format = NSLocalizedStringFromTableInBundle(@"SWC_ALERT_TITLE", swca_string_table, swca_get_security_bundle(), nil);
507 default_button_key = @"SWC_ALLOW_USE";
508 alternate_button_key = @"SWC_CANCEL";
509 info_message_key = @"SWC_INFO_MESSAGE";
510
511 #pragma clang diagnostic push
512 #pragma clang diagnostic ignored "-Wformat-nonliteral"
513 notification_dictionary[(__bridge NSString *)kCFUserNotificationAlertHeaderKey] = [NSString stringWithFormat: request_title_format, client.client_name];
514 #pragma clang diagnostic pop
515 notification_dictionary[(__bridge NSString *)kCFUserNotificationAlertMessageKey] = NSLocalizedStringFromTableInBundle(info_message_key, swca_string_table, swca_get_security_bundle(), nil);
516 notification_dictionary[(__bridge NSString *)kCFUserNotificationDefaultButtonTitleKey] = NSLocalizedStringFromTableInBundle(default_button_key, swca_string_table, swca_get_security_bundle(), nil);
517 notification_dictionary[(__bridge NSString *)kCFUserNotificationAlternateButtonTitleKey] = NSLocalizedStringFromTableInBundle(alternate_button_key, swca_string_table, swca_get_security_bundle(), nil);
518
519 notification_dictionary[(__bridge NSString *)kCFUserNotificationLocalizationURLKey] = [swca_get_security_bundle() bundleURL];
520 notification_dictionary[(__bridge NSString *)kCFUserNotificationAlertTopMostKey] = [NSNumber numberWithBool:YES];
521
522 // additional keys for remote view controller
523 notification_dictionary[(__bridge NSString *)SBUserNotificationDismissOnLock] = [NSNumber numberWithBool:YES];
524 notification_dictionary[(__bridge NSString *)SBUserNotificationDontDismissOnUnlock] = [NSNumber numberWithBool:YES];
525 notification_dictionary[(__bridge NSString *)SBUserNotificationRemoteServiceBundleIdentifierKey] = @"com.apple.SharedWebCredentialViewService";
526 notification_dictionary[(__bridge NSString *)SBUserNotificationRemoteViewControllerClassNameKey] = @"SWCViewController";
527 notification_dictionary[(__bridge NSString *)SBUserNotificationAllowedApplicationsKey] = client.client;
528
529 SInt32 err;
530 if (!(notification = CFUserNotificationCreate(NULL, 0, 0, &err, (__bridge CFDictionaryRef)notification_dictionary)) ||
531 err)
532 goto out;
533 if (CFUserNotificationReceiveResponse(notification, 0, &response))
534 goto out;
535
536 //NSLog(@"Selection: %@, Response: %lu", gActiveItem, (unsigned long)response);
537 if (result && response == kCFUserNotificationDefaultResponse) {
538 CFRetainSafe(gActiveItem);
539 *result = gActiveItem;
540 }
541
542 out:
543 if (alert_sem_held) {
544 dispatch_semaphore_signal(select_alert_sem);
545 }
546 CFReleaseSafe(notification);
547 CFReleaseNull(gActiveArray);
548 CFReleaseNull(gActiveItem);
549
550 return (result && *result);
551 }
552
553 /*
554 * Return a SecTaskRef iff orignal client have the entitlement kSecEntitlementAssociatedDomains
555 */
556
557 static SecTaskRef
558 swcaCopyClientFromMessage(xpc_object_t event, CFErrorRef *error)
559 {
560 SecTaskRef clientTask = NULL;
561 audit_token_t auditToken = {};
562
563 // fetch audit token for process which called securityd
564 size_t length = 0;
565 const uint8_t *bytes = xpc_dictionary_get_data(event, kSecXPCKeyClientToken, &length);
566 if (length != sizeof(audit_token_t)) {
567 SecError(errSecMissingEntitlement, error, CFSTR("swcagent_xpc - wrong length for client id"));
568 return NULL;
569 }
570
571 memcpy(&auditToken, bytes, sizeof(audit_token_t));
572
573 // identify original client
574 clientTask = SecTaskCreateWithAuditToken(kCFAllocatorDefault, auditToken);
575 if (clientTask == NULL) {
576 pid_t pid = 0;
577 audit_token_to_au32(auditToken, NULL, NULL, NULL, NULL, NULL, &pid, NULL, NULL);
578 SecError(errSecMissingEntitlement, error, CFSTR("can't get entitlement of original client pid %d"), (int)pid);
579 return NULL;
580 }
581
582
583 // check for presence of original client's shared credential entitlement
584 CFArrayRef domains = SecTaskCopyArrayOfStringsForEntitlement(clientTask, kSecEntitlementAssociatedDomains);
585 if (domains == NULL) {
586 SecError(errSecMissingEntitlement, error, CFSTR("%@ lacks entitlement %@"),
587 clientTask, kSecEntitlementAssociatedDomains);
588 CFReleaseNull(clientTask);
589 return NULL;
590 } else {
591 CFReleaseNull(domains);
592 }
593
594 return clientTask;
595 }
596
597
598 static void swca_xpc_dictionary_handler(const xpc_connection_t connection, xpc_object_t event) {
599 xpc_type_t type = xpc_get_type(event);
600 __block CFErrorRef error = NULL;
601 xpc_object_t xpcError = NULL;
602 xpc_object_t replyMessage = NULL;
603 SecTaskRef clientTask = NULL;
604 Client* client = NULL;
605 CFArrayRef accessGroups = NULL;
606
607 secdebug("swcagent_xpc", "entering");
608 if (type == XPC_TYPE_DICTIONARY) {
609 bool entitlementOK = false;
610 replyMessage = xpc_dictionary_create_reply(event);
611
612 uint64_t operation = xpc_dictionary_get_uint64(event, kSecXPCKeyOperation);
613 secinfo("swcagent_xpc", "operation: %@ (%" PRIu64 ")", SWCAGetOperationDescription((enum SWCAXPCOperation)operation), operation);
614
615
616 if (operation == swca_copy_pairs_request_id || operation == swca_set_selection_request_id) {
617 /* check entitlement */
618 xpc_object_t ent = xpc_connection_copy_entitlement_value(connection, "com.apple.private.associated-domains");
619 if (ent)
620 entitlementOK = true;
621
622 } else {
623 clientTask = swcaCopyClientFromMessage(event, &error);
624 if (clientTask) {
625 accessGroups = SecTaskCopyAccessGroups(clientTask);
626 client = SecTaskCopyClient(clientTask);
627 CFReleaseNull(clientTask);
628 }
629
630 if (accessGroups && client)
631 entitlementOK = true;
632
633 }
634
635 if (entitlementOK) {
636 switch (operation)
637 {
638 case swca_add_request_id:
639 {
640 CFDictionaryRef query = SecXPCDictionaryCopyDictionary(event, kSecXPCKeyQuery, &error);
641 //secdebug("ipc", "swcagent: got swca_add_request_id, query: %@", query);
642 if (query) {
643 CFTypeRef result = NULL;
644 // confirm that we can add this item
645 if (swca_confirm_add(query, client, accessGroups, &result, &error) && result) {
646 SecXPCDictionarySetPList(replyMessage, kSecXPCKeyResult, result, &error);
647 CFRelease(result);
648 }
649 CFRelease(query);
650 }
651 break;
652 }
653 case swca_copy_request_id:
654 {
655 CFDictionaryRef query = SecXPCDictionaryCopyDictionary(event, kSecXPCKeyQuery, &error);
656 //secdebug("ipc", "swcagent: got swca_copy_request_id, query: %@", query);
657 if (query) {
658 CFTypeRef result = NULL;
659 // confirm that we can copy this item
660 if (swca_confirm_copy(query, client, accessGroups, &result, &error) && result) {
661 SecXPCDictionarySetPList(replyMessage, kSecXPCKeyResult, result, &error);
662 CFRelease(result);
663 }
664 CFRelease(query);
665 }
666 break;
667 }
668 case swca_update_request_id:
669 {
670 CFDictionaryRef query = SecXPCDictionaryCopyDictionary(event, kSecXPCKeyQuery, &error);
671 //secdebug("ipc", "swcagent: got swca_update_request_id, query: %@", query);
672 if (query) {
673 CFTypeRef result = NULL;
674 // confirm that we can copy this item
675 if (swca_confirm_update(query, client, accessGroups, &result, &error) && result) {
676 SecXPCDictionarySetPList(replyMessage, kSecXPCKeyResult, result, &error);
677 CFRelease(result);
678 }
679 CFRelease(query);
680 }
681 break;
682 }
683 case swca_delete_request_id:
684 {
685 CFDictionaryRef query = SecXPCDictionaryCopyDictionary(event, kSecXPCKeyQuery, &error);
686 //secdebug("ipc", "swcagent: got swca_delete_request_id, query: %@", query);
687 if (query) {
688 CFTypeRef result = NULL;
689 // confirm that we can copy this item
690 if (swca_confirm_delete(query, client, accessGroups, &result, &error) && result) {
691 SecXPCDictionarySetPList(replyMessage, kSecXPCKeyResult, result, &error);
692 CFRelease(result);
693 }
694 CFRelease(query);
695 }
696 break;
697 }
698 case swca_select_request_id:
699 {
700 CFArrayRef items = SecXPCDictionaryCopyArray(event, kSecXPCKeyQuery, &error);
701 secdebug("ipc", "swcagent: got swca_select_request_id, items: %@", items);
702 if (items) {
703 CFTypeRef result = NULL;
704 // select a dictionary from an input array of dictionaries
705 if (swca_select_item(items, client, accessGroups, &result, &error) && result) {
706 LAContext *ctx = [LAContext new];
707 NSString *subTitle = NSLocalizedStringFromTableInBundle(@"SWC_FILLPWD", swca_string_table, swca_get_security_bundle(), nil);
708 dispatch_semaphore_t sema = dispatch_semaphore_create(0);
709 [ctx evaluatePolicy:LAPolicyDeviceOwnerAuthentication localizedReason:subTitle reply:^(BOOL success, NSError * _Nullable laError) {
710 if (success || ([laError.domain isEqual:LAErrorDomain] && laError.code == LAErrorPasscodeNotSet))
711 SecXPCDictionarySetPList(replyMessage, kSecXPCKeyResult, result, &error);
712 dispatch_semaphore_signal(sema);
713 }];
714 dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
715 CFRelease(result);
716 }
717 CFRelease(items);
718 }
719 break;
720 }
721 case swca_copy_pairs_request_id:
722 {
723 secdebug("ipc", "swcagent: got swca_copy_pairs_request_id");
724 SecXPCDictionarySetPList(replyMessage, kSecXPCKeyResult, gActiveArray, &error);
725 break;
726 }
727 case swca_set_selection_request_id:
728 {
729 CFDictionaryRef dict = SecXPCDictionaryCopyDictionary(event, kSecXPCKeyQuery, &error);
730 secdebug("ipc", "swcagent: got swca_set_selection_request_id, dict: %@", dict);
731 if (dict) {
732 int32_t value = (int32_t) 1;
733 CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &value);
734 SecXPCDictionarySetPList(replyMessage, kSecXPCKeyResult, number, &error);
735 CFReleaseSafe(number);
736 }
737 CFReleaseSafe(gActiveItem);
738 gActiveItem = dict;
739 break;
740 }
741 case swca_enabled_request_id:
742 {
743 // return Safari's password autofill enabled status
744 CFTypeRef result = (SWCAIsAutofillEnabled()) ? kCFBooleanTrue : kCFBooleanFalse;
745 SecXPCDictionarySetPList(replyMessage, kSecXPCKeyResult, result, &error);
746 }
747 default:
748 secdebug("ipc", "swcagent: got unsupported request id (%ld)", (long)operation);
749 break;
750 }
751 }
752 if (error)
753 {
754 if(SecErrorGetOSStatus(error) == errSecItemNotFound)
755 secdebug("ipc", "%@ %@ %@", clientTask, SWCAGetOperationDescription((enum SWCAXPCOperation)operation), error);
756 else
757 secerror("%@ %@ %@", clientTask, SWCAGetOperationDescription((enum SWCAXPCOperation)operation), error);
758
759 xpcError = SecCreateXPCObjectWithCFError(error);
760 xpc_dictionary_set_value(replyMessage, kSecXPCKeyError, xpcError);
761 } else if (replyMessage) {
762 secdebug("ipc", "%@ %@ responding %@", clientTask, SWCAGetOperationDescription((enum SWCAXPCOperation)operation), replyMessage);
763 }
764 } else {
765 SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType, sSecXPCErrorDomain, NULL, &error, 0, CFSTR("Messages expect to be xpc dictionary, got: %@"), event);
766 secerror("%@: returning error: %@", clientTask, error);
767 xpcError = SecCreateXPCObjectWithCFError(error);
768 replyMessage = xpc_create_reply_with_format(event, "{%string: %value}", kSecXPCKeyError, xpcError);
769 }
770
771 if (replyMessage) {
772 xpc_connection_send_message(connection, replyMessage);
773 }
774 CFReleaseSafe(error);
775 CFReleaseSafe(accessGroups);
776 }
777
778 static xpc_connection_t swclistener = NULL;
779
780 static void swca_xpc_init()
781 {
782 secdebug("swcagent_xpc", "start");
783
784 swclistener = xpc_connection_create_mach_service(kSWCAXPCServiceName, NULL, XPC_CONNECTION_MACH_SERVICE_LISTENER);
785 if (!swclistener) {
786 seccritical("swcagent failed to register xpc listener, exiting");
787 abort();
788 }
789
790 xpc_connection_set_event_handler(swclistener, ^(xpc_object_t connection) {
791 if (xpc_get_type(connection) == XPC_TYPE_CONNECTION) {
792 audit_token_t auditToken = {};
793 SecTaskRef clientTask = NULL;
794
795 /*
796 * check that our caller have the private entitlement to invoke swcagent
797 */
798
799 xpc_connection_get_audit_token(connection, &auditToken);
800 clientTask = SecTaskCreateWithAuditToken(kCFAllocatorDefault, auditToken);
801 if (clientTask == NULL) {
802 secerror("can't get sectask of connection %@", connection);
803 xpc_connection_cancel(connection);
804 return;
805 }
806
807 if (!SecTaskGetBooleanValueForEntitlement(clientTask, kSecEntitlementPrivateAssociatedDomains)) {
808 secerror("client %@ lacks entitlement %@", clientTask, kSecEntitlementPrivateAssociatedDomains);
809 CFReleaseNull(clientTask);
810 xpc_connection_cancel(connection);
811 return;
812 }
813 CFReleaseNull(clientTask);
814
815 xpc_connection_set_event_handler(connection, ^(xpc_object_t event) {
816 if (xpc_get_type(event) == XPC_TYPE_DICTIONARY) {
817 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
818 swca_xpc_dictionary_handler(connection, event);
819 });
820 }
821 });
822 xpc_connection_resume(connection);
823 }
824 });
825 xpc_connection_resume(swclistener);
826 }
827
828 int main(int argc, char *argv[])
829 {
830 @autoreleasepool {
831 char *wait4debugger = getenv("WAIT4DEBUGGER");
832 if (wait4debugger && !strcasecmp("YES", wait4debugger)) {
833 seccritical("SIGSTOPing self, awaiting debugger");
834 kill(getpid(), SIGSTOP);
835 seccritical("Again, for good luck (or bad debuggers)");
836 kill(getpid(), SIGSTOP);
837 }
838 swca_xpc_init();
839 dispatch_main();
840 }
841 }
842
843 /* vi:set ts=4 sw=4 et: */