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