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