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