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
34
35//
36// Construct a CodeSigningHost
37//
38CodeSigningHost::CodeSigningHost()
39	: mLock(Mutex::recursive), mHostingState(noHosting)
40{
41}
42
43
44//
45// Cleanup code.
46//
47CodeSigningHost::~CodeSigningHost()
48{
49	reset();
50}
51
52
53//
54// Reset Code Signing Hosting state.
55// This turns hosting off and clears all children.
56//
57void 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//
86CodeSigningHost::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//
104CodeSigningHost::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//
123CodeSigningHost::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//
169CodeSigningHost::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//
183void 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//
203SecGuestRef 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
255void 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//
279void 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//
301CodeSigningHost::Guest::~Guest()
302{ }
303
304void 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
315CFDataRef CodeSigningHost::Guest::attrData() const
316{
317	if (!mAttrData)
318		mAttrData = makeCFData(this->attributes.get());
319	return mAttrData;
320}
321
322
323void 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
342bool 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//
367bool 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
388struct CodeSigningHost::Lock : private StLock<Mutex> {
389	Lock(CodeSigningHost *host) : StLock<Mutex>(host->mLock) { }
390};
391
392
393boolean_t cshosting_server(mach_msg_header_t *, mach_msg_header_t *);
394
395static ThreadNexus<CodeSigningHost *> context;
396
397boolean_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//
432kern_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//
455kern_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//
482kern_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
495void 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
519void 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
532