]> git.saurik.com Git - apple/security.git/blob - OSX/libsecurity_keychain/xpc/main.c
Security-58286.251.4.tar.gz
[apple/security.git] / OSX / libsecurity_keychain / xpc / main.c
1 /*
2 * Copyright (c) 2011-2014 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24 #include <xpc/private.h>
25 #include <syslog.h>
26 #include <sys/param.h>
27 #include <sandbox.h>
28 #include <dlfcn.h>
29 #include <sysexits.h>
30 #include <Security/Security.h>
31 #include <stdlib.h>
32 #include <libgen.h>
33 #include <string.h>
34 #include <CommonCrypto/CommonDigest.h>
35 #include <CoreFoundation/CoreFoundation.h>
36 #include <sys/types.h>
37 #include <pwd.h>
38
39 struct connection_info {
40 xpc_connection_t peer;
41 int processed;
42 int done;
43 };
44
45 typedef typeof(SecKeychainCopyDomainSearchList) SecKeychainCopyDomainSearchListType;
46 SecKeychainCopyDomainSearchListType *SecKeychainCopyDomainSearchListFunctionPointer = NULL;
47 typedef typeof(SecKeychainGetPath) SecKeychainGetPathType;
48 SecKeychainGetPathType *SecKeychainGetPathFunctionPointer = NULL;
49
50 // prior to 8723022 we have to do our own idle timeout work
51 #ifndef XPC_HANDLES_IDLE_TIMEOUT
52 int current_connections = 0;
53 // Number of seconds to sit with no clients
54 #define IDLE_WAIT_TIME 30
55 #endif
56
57 xpc_object_t keychain_prefs_path = NULL;
58 xpc_object_t home = NULL;
59
60 extern xpc_object_t
61 xpc_create_reply_with_format(xpc_object_t original, const char * format, ...);
62
63 static
64 xpc_object_t create_keychain_search_list_for(xpc_connection_t peer, SecPreferencesDomain domain)
65 {
66 CFArrayRef keychains = NULL;
67 pid_t peer_pid = xpc_connection_get_pid(peer);
68 OSStatus status = SecKeychainCopyDomainSearchListFunctionPointer(domain, &keychains);
69 if (errSecSuccess != status) {
70 syslog(LOG_ERR, "Unable to get keychain search list (domain=%d) on behalf of %d, status=0x%lx", domain, peer_pid, (unsigned long)status);
71 return NULL;
72 }
73
74 xpc_object_t paths = xpc_array_create(NULL, 0);
75 CFIndex n_keychains = CFArrayGetCount(keychains);
76 CFIndex i;
77 for(i = 0; i < n_keychains; i++) {
78 char path[MAXPATHLEN];
79
80 SecKeychainRef keychain = (SecKeychainRef)CFArrayGetValueAtIndex(keychains, i);
81 UInt32 length = MAXPATHLEN;
82 OSStatus status = SecKeychainGetPathFunctionPointer(keychain, &length, path);
83 if (errSecSuccess != status) {
84 syslog(LOG_ERR, "Unable to get path for keychain#%ld of %ld on behalf of %d, status=0x%lx", i, n_keychains, peer_pid, (unsigned long)status);
85 continue;
86 }
87 xpc_object_t path_as_xpc_string = xpc_string_create(path);
88 xpc_array_append_value(paths, path_as_xpc_string);
89 xpc_release(path_as_xpc_string);
90 }
91 CFRelease(keychains);
92 return paths;
93 }
94
95 static
96 bool keychain_domain_needs_writes(const char *domain_name)
97 {
98 return (0 == strcmp("kSecPreferencesDomainUser", domain_name) || 0 == strcmp("kSecPreferencesDomainDynamic", domain_name));
99 }
100
101 static
102 void _set_keychain_search_lists_for_domain(xpc_connection_t peer, xpc_object_t all_domains, char *domain_name, SecPreferencesDomain domain_enum)
103 {
104 xpc_object_t keychains_for_domain = create_keychain_search_list_for(peer, domain_enum);
105 if (keychains_for_domain) {
106 xpc_dictionary_set_value(all_domains, domain_name, keychains_for_domain);
107 xpc_release(keychains_for_domain);
108 } else {
109 syslog(LOG_ERR, "Can't discover keychain paths for domain %s on behalf of %d", domain_name, xpc_connection_get_pid(peer));
110 }
111 }
112
113 #define SET_KEYCHAIN_SEARCH_LISTS_FOR_DOMAIN(peer, all_domains, domain) _set_keychain_search_lists_for_domain(peer, all_domains, #domain, domain);
114
115 static
116 xpc_object_t create_keychain_search_lists(xpc_connection_t peer)
117 {
118 xpc_object_t all_domains = xpc_dictionary_create(NULL, NULL, 0);
119
120 SET_KEYCHAIN_SEARCH_LISTS_FOR_DOMAIN(peer, all_domains, kSecPreferencesDomainUser);
121 SET_KEYCHAIN_SEARCH_LISTS_FOR_DOMAIN(peer, all_domains, kSecPreferencesDomainSystem);
122 SET_KEYCHAIN_SEARCH_LISTS_FOR_DOMAIN(peer, all_domains, kSecPreferencesDomainCommon);
123 SET_KEYCHAIN_SEARCH_LISTS_FOR_DOMAIN(peer, all_domains, kSecPreferencesDomainDynamic);
124
125 return all_domains;
126 }
127
128
129 static
130 xpc_object_t create_keychain_and_lock_paths(xpc_connection_t peer, xpc_object_t keychain_path_dict)
131 {
132 pid_t peer_pid = xpc_connection_get_pid(peer);
133 char *assembly_queue_label = NULL;
134 asprintf(&assembly_queue_label, "assembly-for-%d", peer_pid);
135 if (!assembly_queue_label) {
136 syslog(LOG_ERR, "Unable to create assembly queue label for %d", peer_pid);
137 return NULL;
138 }
139 dispatch_queue_t assembly_queue = dispatch_queue_create(assembly_queue_label, 0);
140 free(assembly_queue_label);
141 if (!assembly_queue) {
142 syslog(LOG_ERR, "Unable to create assembly queue for %d", peer_pid);
143 return NULL;
144 }
145 xpc_object_t return_paths_dict = xpc_dictionary_create(NULL, NULL, 0);
146
147 xpc_dictionary_apply(keychain_path_dict, ^(const char *keychain_domain, xpc_object_t keychain_path_array) {
148 xpc_object_t return_paths_array = xpc_array_create(NULL, 0);
149 dispatch_apply(xpc_array_get_count(keychain_path_array), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t i) {
150 xpc_object_t path_as_xpc_string = xpc_array_get_value(keychain_path_array, i);
151 dispatch_sync(assembly_queue, ^{
152 xpc_array_append_value(return_paths_array, path_as_xpc_string);
153 });
154
155 if (!keychain_domain_needs_writes(keychain_domain)) {
156 // lock files are only to prevent write-write errors, readers don't hold locks, so they don't need the lock files
157 return;
158 }
159
160 // figure out the base and dir
161 const char* path = xpc_array_get_string(keychain_path_array, i);
162 char* dir = NULL;
163 char* base = NULL;
164
165 char buffer[PATH_MAX];
166 strcpy(buffer, path);
167
168 if (path != NULL) {
169 ptrdiff_t i = strlen(buffer) - 1;
170 while (i >= 0 && buffer[i] != '/') {
171 i -= 1;
172 }
173
174 if (i >= 0) {
175 // NULL terminate the dir
176 buffer[i] = 0;
177 dir = buffer;
178 base = buffer + i + 1;
179 } else {
180 dir = NULL;
181 base = buffer;
182 }
183 }
184
185 if (!(path && dir && base)) {
186 syslog(LOG_ERR, "Can't get dir or base (likely out of memory) for %s", xpc_array_get_string(keychain_path_array, i));
187 return;
188 }
189
190 // "network style" lock files
191 path_as_xpc_string = xpc_string_create_with_format("%s/lck~%s", dir, base);
192 dispatch_sync(assembly_queue, ^{
193 xpc_array_append_value(return_paths_array, path_as_xpc_string);
194 });
195 xpc_release(path_as_xpc_string);
196
197 CC_SHA1_CTX sha1Context;
198 CC_SHA1_Init(&sha1Context);
199 CC_SHA1_Update(&sha1Context, base, (CC_LONG)strlen(base));
200
201 unsigned char sha1_result_bytes[CC_SHA1_DIGEST_LENGTH];
202
203 CC_SHA1_Final(sha1_result_bytes, &sha1Context);
204
205 path_as_xpc_string = xpc_string_create_with_format("%s/.fl%02X%02X%02X%02X", dir, sha1_result_bytes[0], sha1_result_bytes[1], sha1_result_bytes[2], sha1_result_bytes[3]);
206 dispatch_sync(assembly_queue, ^{
207 xpc_array_append_value(return_paths_array, path_as_xpc_string);
208 });
209 });
210 xpc_dictionary_set_value(return_paths_dict, keychain_domain, return_paths_array);
211 xpc_release(return_paths_array);
212 return (bool)true;
213 });
214
215 dispatch_release(assembly_queue);
216 return return_paths_dict;
217 }
218
219 static
220 xpc_object_t create_one_sandbox_extension(xpc_object_t path, bool read_only)
221 {
222 const char * extension_class = read_only ? APP_SANDBOX_READ : APP_SANDBOX_READ_WRITE;
223 char *sandbox_extension = sandbox_extension_issue_file(extension_class, xpc_string_get_string_ptr(path), SANDBOX_EXTENSION_CANONICAL);
224 if (sandbox_extension) {
225 xpc_object_t sandbox_extension_as_xpc_string = xpc_string_create(sandbox_extension);
226 free(sandbox_extension);
227 return sandbox_extension_as_xpc_string;
228 } else {
229 syslog(LOG_ERR, "Can't get sandbox fs extension for %s", xpc_string_get_string_ptr(path));
230 }
231 return NULL;
232 }
233
234 static
235 xpc_object_t create_all_sandbox_extensions(xpc_object_t path_dict)
236 {
237 xpc_object_t extensions = xpc_array_create(NULL, 0);
238
239 xpc_object_t sandbox_extension = create_one_sandbox_extension(keychain_prefs_path, true);
240 if (sandbox_extension) {
241 xpc_array_append_value(extensions, sandbox_extension);
242 xpc_release(sandbox_extension);
243 }
244
245 xpc_dictionary_apply(path_dict, ^(const char *keychain_domain, xpc_object_t path_array) {
246 bool read_only = true;
247 if (keychain_domain_needs_writes(keychain_domain)) {
248 read_only = false;
249 }
250 xpc_array_apply(path_array, ^(size_t index, xpc_object_t path) {
251 xpc_object_t sandbox_extension = create_one_sandbox_extension(path, read_only);
252 if (sandbox_extension) {
253 xpc_array_append_value(extensions, sandbox_extension);
254 xpc_release(sandbox_extension);
255 }
256 return (bool)true;
257 });
258 return (bool)true;
259 });
260
261 return extensions;
262 }
263
264 static
265 void handle_request_event(struct connection_info *info, xpc_object_t event)
266 {
267 xpc_connection_t peer = xpc_dictionary_get_remote_connection(event);
268 xpc_type_t xtype = xpc_get_type(event);
269 if (info->done) {
270 syslog(LOG_ERR, "event %p while done", event);
271 return;
272 }
273 if (xtype == XPC_TYPE_ERROR) {
274 if (event == XPC_ERROR_TERMINATION_IMMINENT) {
275 // launchd would like us to die, but we have open transactions. When we finish with them xpc_service_main
276 // will exit for us, so there is nothing for us to do here.
277 return;
278 }
279
280 if (!info->done) {
281 info->done = true;
282 xpc_release(info->peer);
283 #ifndef XPC_HANDLES_IDLE_TIMEOUT
284 if (0 == __sync_add_and_fetch(&current_connections, -1)) {
285 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * IDLE_WAIT_TIME), dispatch_get_main_queue(), ^(void) {
286 if (0 == current_connections) {
287 exit(0);
288 }
289 });
290 }
291 #endif
292 }
293 if (peer == NULL && XPC_ERROR_CONNECTION_INVALID == event && 0 != info->processed) {
294 // this is a normal shutdown on a connection that has processed at least
295 // one request. Nothing intresting to log.
296 return;
297 }
298 syslog(LOG_ERR, "listener event error (connection %p): %s", peer, xpc_dictionary_get_string(event, XPC_ERROR_KEY_DESCRIPTION));
299 } else if (xtype == XPC_TYPE_DICTIONARY) {
300 const char *operation = xpc_dictionary_get_string(event, "op");
301 if (operation && !strcmp(operation, "GrantKeychainPaths")) {
302 xpc_object_t keychain_paths = create_keychain_search_lists(peer);
303 xpc_object_t all_paths = create_keychain_and_lock_paths(peer, keychain_paths);
304 xpc_object_t sandbox_extensions = NULL;
305 if (all_paths) {
306 sandbox_extensions = create_all_sandbox_extensions(all_paths);
307 }
308
309 xpc_object_t reply = xpc_create_reply_with_format(event, "{keychain-paths: %value, all-paths: %value, extensions: %value, keychain-home: %value}",
310 keychain_paths, all_paths, sandbox_extensions, home);
311 xpc_connection_send_message(peer, reply);
312 xpc_release(reply);
313 if (keychain_paths) {
314 xpc_release(keychain_paths);
315 }
316 if (all_paths) {
317 xpc_release(all_paths);
318 }
319 if (sandbox_extensions) {
320 xpc_release(sandbox_extensions);
321 }
322 if (INT32_MAX != info->processed) {
323 info->processed++;
324 }
325 } else {
326 syslog(LOG_ERR, "Unknown op=%s request from pid %d", operation, xpc_connection_get_pid(peer));
327 }
328 } else {
329 syslog(LOG_ERR, "Unhandled request event=%p type=%p", event, xtype);
330 }
331 }
332
333 static
334 void finalize_connection(void *not_used)
335 {
336 #ifdef XPC_HANDLES_IDLE_TIMEOUT
337 xpc_transaction_end();
338 #endif
339 }
340
341 static
342 void handle_connection_event(const xpc_connection_t peer)
343 {
344 #ifndef XPC_HANDLES_IDLE_TIMEOUT
345 __sync_add_and_fetch(&current_connections, 1);
346 #endif
347 __block struct connection_info info;
348 info.peer = peer;
349 info.processed = 0;
350 info.done = false;
351
352 xpc_connection_set_event_handler(peer, ^(xpc_object_t event) {
353 handle_request_event(&info, event);
354 });
355
356 // unlike dispatch objects xpc objects don't need a context set in order to run a finalizer. (we use our finalizer to
357 // end the transaction we are about to begin...this keeps xpc from idle exiting us while we have a live connection)
358 xpc_connection_set_finalizer_f(peer, finalize_connection);
359 #ifdef XPC_HANDLES_IDLE_TIMEOUT
360 xpc_transaction_begin();
361 #endif
362
363 // enable the peer connection to receive messages
364 xpc_connection_resume(peer);
365 xpc_retain(peer);
366 }
367
368
369 static const char* g_path_to_plist = "/Library/Preferences/com.apple.security.plist";
370
371
372
373 int main(int argc, const char *argv[])
374 {
375 char *wait4debugger = getenv("WAIT4DEBUGGER");
376 if (wait4debugger && !strcasecmp("YES", wait4debugger)) {
377 syslog(LOG_ERR, "Waiting for debugger");
378 kill(getpid(), SIGSTOP);
379 }
380
381 // get the home directory
382 const char* home_dir = getenv("HOME");
383
384 if (home_dir == NULL || strlen(home_dir) == 0) {
385 struct passwd* pwd = getpwuid(getuid());
386 home_dir = pwd->pw_dir; // look it up in directory services, sort of...
387 }
388
389 size_t home_dir_length = strlen(home_dir);
390 size_t path_to_plist_length = strlen(g_path_to_plist);
391
392 size_t total_length = home_dir_length + path_to_plist_length + 1; // compensate for terminating zero
393 if (total_length > PATH_MAX) {
394 // someone is spoofing us, just exit
395 return -1;
396 }
397
398 // make storage for the real path
399 char buffer[PATH_MAX];
400 strlcpy(buffer, home_dir, sizeof(buffer));
401 strlcat(buffer, g_path_to_plist, sizeof(buffer));
402 keychain_prefs_path = xpc_string_create(buffer);
403 home = xpc_string_create(home_dir);
404
405 void *security_framework = dlopen("/System/Library/Frameworks/Security.framework/Security", RTLD_LAZY);
406 if (security_framework) {
407 SecKeychainCopyDomainSearchListFunctionPointer = dlsym(security_framework, "SecKeychainCopyDomainSearchList");
408 if (!SecKeychainCopyDomainSearchListFunctionPointer) {
409 syslog(LOG_ERR, "Can't lookup SecKeychainCopyDomainSearchList in %p: %s", security_framework, dlerror());
410 return EX_OSERR;
411 }
412 SecKeychainGetPathFunctionPointer = dlsym(security_framework, "SecKeychainGetPath");
413 if (!SecKeychainGetPathFunctionPointer) {
414 syslog(LOG_ERR, "Can't lookup SecKeychainGetPath in %p: %s", security_framework, dlerror());
415 return EX_OSERR;
416 }
417 } else {
418 syslog(LOG_ERR, "Failed to open Security framework: %s", dlerror());
419 return EX_OSERR;
420 }
421
422 xpc_main(handle_connection_event);
423
424 return EX_OSERR;
425 }