]> git.saurik.com Git - apple/securityd.git/blob - src/csproxy.cpp
securityd-32661.tar.gz
[apple/securityd.git] / src / csproxy.cpp
1 /*
2 * Copyright (c) 2006-2007 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 //
26 // csproxy - Code Signing Hosting Proxy
27 //
28 #include "csproxy.h"
29 #include "server.h"
30 #include <securityd_client/cshosting.h>
31
32
33 //
34 // Construct a CodeSigningHost
35 //
36 CodeSigningHost::CodeSigningHost()
37 : mHostingState(noHosting)
38 {
39 }
40
41
42 //
43 // Cleanup code.
44 //
45 CodeSigningHost::~CodeSigningHost()
46 {
47 reset();
48 }
49
50
51 //
52 // Reset Code Signing Hosting state.
53 // This turns hosting off and clears all children.
54 //
55 void CodeSigningHost::reset()
56 {
57 switch (mHostingState) {
58 case noHosting:
59 break; // nothing to do
60 case dynamicHosting:
61 mHostingPort.destroy();
62 mHostingPort = MACH_PORT_NULL;
63 break;
64 case proxyHosting:
65 Server::active().remove(*this); // unhook service handler
66 mHostingPort.destroy(); // destroy receive right
67 mHostingState = noHosting;
68 mHostingPort = MACH_PORT_NULL;
69 mGuests.erase(mGuests.begin(), mGuests.end());
70 break;
71 }
72 }
73
74
75 //
76 // Given a host reference (possibly NULL for the process itself), locate
77 // its most dedicated guest. This descends a contiguous chain of dedicated
78 // guests until it find a host that either has no guests, or whose guests
79 // are not dedicated.
80 //
81 CodeSigningHost::Guest *CodeSigningHost::findHost(SecGuestRef hostRef)
82 {
83 Guest *host = findGuest(hostRef, true);
84 for (;;) {
85 if (Guest *guest = findGuest(host))
86 if (guest->dedicated) {
87 secdebug("hosting", "%p selecting dedicated guest %p of %p", this, guest, host);
88 host = guest;
89 continue;
90 }
91 return host;
92 }
93 }
94
95
96 //
97 // Look up guest by guestRef.
98 // Throws if they we don't have a guest by that ref.
99 //
100 CodeSigningHost::Guest *CodeSigningHost::findGuest(SecGuestRef guestRef, bool hostOk /* = false */)
101 {
102 GuestMap::iterator it = mGuests.find(guestRef);
103 if (it == mGuests.end())
104 if (hostOk)
105 return NULL;
106 else
107 MacOSError::throwMe(errSecCSNoSuchCode);
108 assert(it->first == it->second->guestRef());
109 return it->second;
110 }
111
112
113 //
114 // Look up guest by attribute set.
115 // Returns the host if the attributes can't be found (*loose* interpretation).
116 // Throws if multiple guests are found (ambiguity).
117 // Implicitly matches dedicated guests no matter what attributes are requested.
118 //
119 CodeSigningHost::Guest *CodeSigningHost::findGuest(Guest *host, const CssmData &attrData)
120 {
121 CFRef<CFDictionaryRef> attrDict = attrData
122 ? makeCFDictionaryFrom(attrData.data(), attrData.length())
123 : makeCFDictionary(0);
124 CFDictionary attrs(attrDict, errSecCSInvalidAttributeValues);
125
126 // if a guest handle was provided, start with that - it must be valid or we fail
127 if (CFNumberRef canonical = attrs.get<CFNumberRef>(kSecGuestAttributeCanonical)) {
128 // direct lookup by SecGuestRef (canonical guest handle)
129 SecGuestRef guestRef = cfNumber<SecGuestRef>(canonical);
130 secdebug("hosting", "host %p looking for guest handle 0x%x", host, guestRef);
131 if (Guest *guest = findGuest(guestRef, true)) // found guest handle
132 if (guest->isGuestOf(host, loose)) {
133 secdebug("hosting", "found guest %p, continuing search", guest);
134 host = guest; // new starting point
135 } else
136 MacOSError::throwMe(errSecCSNoSuchCode); // not a guest of given host
137 else
138 MacOSError::throwMe(errSecCSNoSuchCode); // not there at all
139 }
140
141 // now take the rest of the attrs
142 CFIndex count = CFDictionaryGetCount(attrs);
143 CFTypeRef keys[count], values[count];
144 CFDictionaryGetKeysAndValues(attrs, keys, values);
145 for (;;) {
146 secdebug("hosting", "searching host %p by attributes", host);
147 Guest *match = NULL; // previous match found
148 for (GuestMap::const_iterator it = mGuests.begin(); it != mGuests.end(); ++it)
149 if (it->second->isGuestOf(host, strict))
150 if (it->second->matches(count, keys, values))
151 if (match)
152 MacOSError::throwMe(errSecCSMultipleGuests); // ambiguous
153 else
154 match = it->second;
155 if (!match) { // nothing found
156 secdebug("hosting", "nothing found, returning %p", host);
157 return host;
158 } else {
159 secdebug("hosting", "found guest %p, continuing", match);
160 host = match; // and repeat
161 }
162 }
163 }
164
165
166 //
167 // Find any guest of a given host.
168 // This will return a randomly chosen guest of this host if it has any,
169 // or NULL if it has none (i.e. it is not a host).
170 //
171 CodeSigningHost::Guest *CodeSigningHost::findGuest(Guest *host)
172 {
173 for (GuestMap::const_iterator it = mGuests.begin(); it != mGuests.end(); ++it)
174 if (it->second->isGuestOf(host, strict))
175 return it->second;
176 return NULL;
177 }
178
179
180 //
181 // Register a hosting API service port where the host will dynamically
182 // answer hosting queries from interested parties. This switches the process
183 // to dynamic hosting mode, and is incompatible with proxy hosting.
184 //
185 void CodeSigningHost::registerCodeSigning(mach_port_t hostingPort, SecCSFlags flags)
186 {
187 switch (mHostingState) {
188 case noHosting:
189 secdebug("hosting", "%p registering for dynamic hosting on port %d",
190 this, hostingPort);
191 mHostingPort = hostingPort;
192 mHostingState = dynamicHosting;
193 break;
194 default:
195 MacOSError::throwMe(errSecCSHostProtocolContradiction);
196 }
197 }
198
199
200 //
201 // Create a guest entry for the given host and prepare to answer for it
202 // when dynamic hosting queries are received for it.
203 // This engages proxy hosting mode, and is incompatible with dynamic hosting mode.
204 //
205 SecGuestRef CodeSigningHost::createGuest(SecGuestRef hostRef,
206 uint32_t status, const char *path, const CssmData &attributes, SecCSFlags flags)
207 {
208 secdebug("hosting", "%p create guest from host %d", this, hostRef);
209
210 if (path[0] != '/') // relative path (relative to what? :-)
211 MacOSError::throwMe(errSecCSHostProtocolRelativePath);
212
213 // set up for hosting proxy services if nothing's there yet
214 switch (mHostingState) {
215 case noHosting:
216 // set up proxy hosting
217 mHostingPort.allocate();
218 MachServer::Handler::port(mHostingPort);
219 MachServer::active().add(*this);
220 mHostingState = proxyHosting;
221 secdebug("hosting", "%p created hosting port %d for proxy hosting", this, mHostingPort.port());
222 break;
223 case proxyHosting:
224 break; // all set
225 case dynamicHosting:
226 MacOSError::throwMe(errSecCSHostProtocolContradiction);
227 }
228
229 RefPointer<Guest> host = findHost(hostRef);
230 RefPointer<Guest> knownGuest = findGuest(host);
231 if ((flags & kSecCSDedicatedHost) && knownGuest)
232 MacOSError::throwMe(errSecCSHostProtocolDedicationError); // can't dedicate with other guests
233 else if (knownGuest && knownGuest->dedicated)
234 MacOSError::throwMe(errSecCSHostProtocolDedicationError); // other guest is already dedicated
235
236 // create the new guest
237 RefPointer<Guest> guest = new Guest;
238 if (host)
239 guest->guestPath = host->guestPath;
240 guest->guestPath.push_back(guest->handle());
241 guest->status = status;
242 guest->path = path;
243 guest->setAttributes(attributes);
244 guest->dedicated = (flags & kSecCSDedicatedHost);
245 mGuests[guest->guestRef()] = guest;
246 secdebug("hosting", "guest 0x%x created %sstatus=0x%x path=%s",
247 guest->guestRef(), guest->dedicated ? "dedicated " : "", guest->status, guest->path.c_str());
248 return guest->guestRef();
249 }
250
251
252 void CodeSigningHost::setGuestStatus(SecGuestRef guestRef, uint32_t status, const CssmData &attributes)
253 {
254 secdebug("hosting", "%p set guest 0x%x", this, guestRef);
255 if (mHostingState != proxyHosting)
256 MacOSError::throwMe(errSecCSHostProtocolNotProxy);
257 Guest *guest = findGuest(guestRef);
258
259 // state modification machine
260 if ((status & ~guest->status) & CS_VALID)
261 MacOSError::throwMe(errSecCSHostProtocolStateError); // can't set
262 if ((~status & guest->status) & (CS_HARD | CS_KILL))
263 MacOSError::throwMe(errSecCSHostProtocolStateError); // can't clear
264 guest->status = status;
265
266 // replace attributes if requested
267 if (attributes)
268 guest->setAttributes(attributes);
269 }
270
271
272 //
273 // Remove a guest previously introduced via createGuest().
274 //
275 void CodeSigningHost::removeGuest(SecGuestRef hostRef, SecGuestRef guestRef)
276 {
277 secdebug("hosting", "%p removes guest %d from host %d", this, guestRef, hostRef);
278 if (mHostingState != proxyHosting)
279 MacOSError::throwMe(errSecCSHostProtocolNotProxy);
280 RefPointer<Guest> host = findHost(hostRef);
281 RefPointer<Guest> guest = findGuest(guestRef);
282 if (guest->dedicated) // can't remove a dedicated guest
283 MacOSError::throwMe(errSecCSHostProtocolDedicationError);
284 if (!guest->isGuestOf(host, strict))
285 MacOSError::throwMe(errSecCSHostProtocolUnrelated);
286 for (GuestMap::iterator it = mGuests.begin(); it != mGuests.end(); ++it)
287 if (it->second->isGuestOf(guest, loose))
288 mGuests.erase(it);
289 }
290
291
292 //
293 // The internal Guest object
294 //
295 CodeSigningHost::Guest::~Guest()
296 {
297 secdebug("hosting", "guest %ld destroyed", handle());
298 }
299
300 void CodeSigningHost::Guest::setAttributes(const CssmData &attrData)
301 {
302 CFRef<CFNumberRef> guest = makeCFNumber(guestRef());
303 if (attrData) {
304 CFRef<CFDictionaryRef> inputDict = makeCFDictionaryFrom(attrData.data(), attrData.length());
305 CFRef<CFMutableDictionaryRef> dict = CFDictionaryCreateMutableCopy(NULL, 0, inputDict);
306 CFDictionaryAddValue(dict, kSecGuestAttributeCanonical, guest);
307 attributes.take(dict);
308 } else {
309 attributes.take(makeCFDictionary(1, kSecGuestAttributeCanonical, guest.get()));
310 }
311 }
312
313
314 bool CodeSigningHost::Guest::isGuestOf(Guest *host, GuestCheck check) const
315 {
316 vector<SecGuestRef> hostPath;
317 if (host)
318 hostPath = host->guestPath;
319 if (hostPath.size() <= guestPath.size()
320 && !memcmp(&hostPath[0], &guestPath[0], sizeof(SecGuestRef) * hostPath.size()))
321 // hostPath is a prefix of guestPath
322 switch (check) {
323 case loose:
324 return true;
325 case strict:
326 return guestPath.size() == hostPath.size() + 1; // immediate guest
327 }
328 return false;
329 }
330
331
332 //
333 // Check to see if a given guest matches the (possibly empty) attribute set provided
334 // (in broken-open form, for efficiency). A dedicated guest will match all attribute
335 // specifications, even empty ones. A non-dedicated guest matches if at least one
336 // attribute value requested matches exactly (in the sense of CFEqual) that given
337 // by the host for this guest.
338 //
339 bool CodeSigningHost::Guest::matches(CFIndex count, CFTypeRef keys[], CFTypeRef values[]) const
340 {
341 if (dedicated)
342 return true;
343 for (CFIndex n = 0; n < count; n++) {
344 CFStringRef key = CFStringRef(keys[n]);
345 if (CFEqual(key, kSecGuestAttributeCanonical)) // ignore canonical attribute (handled earlier)
346 continue;
347 if (CFTypeRef value = CFDictionaryGetValue(attributes, key))
348 if (CFEqual(value, values[n]))
349 return true;
350 }
351 return false;
352 }
353
354
355 //
356 // The MachServer dispatch handler for proxy hosting.
357 //
358 boolean_t cshosting_server(mach_msg_header_t *, mach_msg_header_t *);
359
360 static ThreadNexus<CodeSigningHost *> context;
361
362 boolean_t CodeSigningHost::handle(mach_msg_header_t *in, mach_msg_header_t *out)
363 {
364 context() = this;
365 return cshosting_server(in, out);
366 }
367
368
369 //
370 // Proxy implementation of Code Signing Hosting protocol
371 //
372 #define CSH_ARGS mach_port_t servicePort, mach_port_t replyPort, OSStatus *rcode
373
374 #define BEGIN_IPC try {
375 #define END_IPC *rcode = noErr; } \
376 catch (const CommonError &err) { *rcode = err.osStatus(); } \
377 catch (...) { *rcode = errSecCSInternalError; } \
378 return KERN_SUCCESS;
379
380 #define DATA_IN(base) void *base, mach_msg_type_number_t base##Length
381 #define DATA_OUT(base) void **base, mach_msg_type_number_t *base##Length
382 #define DATA(base) CssmData(base, base##Length)
383
384
385 //
386 // Find a guest by arbitrary attribute set.
387 //
388 // This returns an array of canonical guest references describing the path
389 // from the host given to the guest found. If the host itself is returned
390 // as a guest, this will be an empty array (zero length).
391 //
392 // The subhost return argument may in the future return the hosting port for
393 // a guest who dynamically manages its hosting (thus breaking out of proxy mode),
394 // but this is not yet implemented.
395 //
396 kern_return_t cshosting_server_findGuest(CSH_ARGS, SecGuestRef hostRef,
397 DATA_IN(attributes),
398 GuestChain *foundGuest, mach_msg_type_number_t *depth, mach_port_t *subhost)
399 {
400 BEGIN_IPC
401
402 *subhost = MACH_PORT_NULL; // preset no sub-hosting port returned
403
404 Process::Guest *host = context()->findGuest(hostRef, true);
405 if (Process::Guest *guest = context()->findGuest(host, DATA(attributes))) {
406 *foundGuest = &guest->guestPath[0];
407 *depth = guest->guestPath.size();
408 } else {
409 *foundGuest = NULL;
410 *depth = 0;
411 }
412 END_IPC
413 }
414
415
416 //
417 // Retrieve the path to a guest specified by canonical reference.
418 //
419 kern_return_t cshosting_server_guestPath(CSH_ARGS, SecGuestRef guestRef, char *path)
420 {
421 BEGIN_IPC
422 strncpy(path, context()->findGuest(guestRef)->path.c_str(), MAXPATHLEN);
423 END_IPC
424 }
425
426
427 //
428 // Retrieve the status word for a guest specified by canonical reference.
429 //
430 kern_return_t cshosting_server_guestStatus(CSH_ARGS, SecGuestRef guestRef, uint32_t *status)
431 {
432 BEGIN_IPC
433 *status = context()->findGuest(guestRef)->status;
434 END_IPC
435 }
436
437
438 //
439 // Debug support
440 //
441 #if defined(DEBUGDUMP)
442
443 void CodeSigningHost::dump() const
444 {
445 switch (mHostingState) {
446 case noHosting:
447 break;
448 case dynamicHosting:
449 Debug::dump(" dynamic host port=%d", mHostingPort.port());
450 break;
451 case proxyHosting:
452 Debug::dump(" proxy-host port=%d", mHostingPort.port());
453 if (!mGuests.empty()) {
454 Debug::dump(" %d guests={", int(mGuests.size()));
455 for (GuestMap::const_iterator it = mGuests.begin(); it != mGuests.end(); ++it) {
456 if (it != mGuests.begin())
457 Debug::dump(", ");
458 it->second->dump();
459 }
460 Debug::dump("}");
461 }
462 break;
463 }
464 }
465
466 void CodeSigningHost::Guest::dump() const
467 {
468 Debug::dump("%s[", path.c_str());
469 for (vector<SecGuestRef>::const_iterator it = guestPath.begin(); it != guestPath.end(); ++it) {
470 if (it != guestPath.begin())
471 Debug::dump("/");
472 Debug::dump("0x%x", *it);
473 }
474 Debug::dump("; status=0x%x attrs=%s]",
475 status, cfString(CFCopyDescription(attributes), true).c_str());
476 }
477
478 #endif //DEBUGDUMP