Code.cpp   [plain text]


/*
 * Copyright (c) 2006-2007 Apple Inc. All Rights Reserved.
 * 
 * @APPLE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */

//
// Code - SecCode API objects
//
#include "Code.h"
#include "StaticCode.h"
#include <Security/SecCodeHost.h>
#include "cskernel.h"
#include <security_utilities/cfmunge.h>
#include <security_utilities/debugging.h>

namespace Security {
namespace CodeSigning {


//
// Construction
//
SecCode::SecCode(SecCode *host)
	: mHost(host), mIdentified(false)
{
	CODESIGN_DYNAMIC_CREATE(this, host);
}


//
// Clean up a SecCode object
//
SecCode::~SecCode() throw()
{
}


//
// CF-level comparison of SecStaticCode objects compares CodeDirectory hashes if signed,
// and falls back on comparing canonical paths if (both are) not.
//
bool SecCode::equal(SecCFObject &secOther)
{
	SecCode *other = static_cast<SecCode *>(&secOther);
	CFDataRef mine = this->cdHash();
	CFDataRef his = other->cdHash();
	if (mine || his)
		return mine && his && CFEqual(mine, his);
	else
		return this->staticCode()->equal(*other->staticCode());
}

CFHashCode SecCode::hash()
{
	if (CFDataRef h = this->cdHash())
		return CFHash(h);
	else
		return this->staticCode()->hash();
}


//
// Yield the host Code
//
SecCode *SecCode::host() const
{
	return mHost;
}


//
// Yield the static code. This is cached.
// The caller does not own the object returned; it lives (at least) as long
// as the SecCode it was derived from.
//
SecStaticCode *SecCode::staticCode()
{
	if (!mIdentified) {
		this->identify();
		mIdentified = true;
	}
	assert(mStaticCode);
	return mStaticCode;
}


//
// Yield the CodeDirectory hash as presented by our host.
// This usually is the same as the hash of staticCode().codeDirectory(), but might not
// if files are changing on disk while code is running.
//
CFDataRef SecCode::cdHash()
{
	if (!mIdentified) {
		this->identify();
		mIdentified = true;
	}
	return mCDHash;		// can be NULL (host has no dynamic identity for guest)
}


//
// Retrieve current dynamic status.
//
SecCodeStatus SecCode::status()
{
	if (this->isRoot())
		return kSecCodeStatusValid;			// root of trust, presumed valid
	else
		return this->host()->getGuestStatus(this);
}

void SecCode::status(SecCodeStatusOperation operation, CFDictionaryRef arguments)
{
	if (this->isRoot())
		MacOSError::throwMe(errSecCSHostProtocolStateError);
	else
		this->host()->changeGuestStatus(this, operation, arguments);
}


//
// By default, we have no guests
//
SecCode *SecCode::locateGuest(CFDictionaryRef)
{
	return NULL;
}


//
// By default, we self-identify by asking our host to identify us.
// (This is currently only overridden in the root-of-trust (kernel) implementation.)
//
void SecCode::identify()
{
	mStaticCode.take(host()->identifyGuest(this, &mCDHash.aref()));
}


//
// The default implementation cannot map guests to disk
//
SecStaticCode *SecCode::identifyGuest(SecCode *, CFDataRef *)
{
	MacOSError::throwMe(errSecCSNoSuchCode);
}


//
// Master validation function.
//
// This is the most important function in all of Code Signing. It performs
// dynamic validation on running code. Despite its simple structure, it does
// everything that's needed to establish whether a Code is currently valid...
// with a little help from StaticCode, format drivers, type drivers, and so on.
//
// This function validates internal requirements in the hosting chain. It does
// not validate external requirements - the caller needs to do that with a separate call.
//
void SecCode::checkValidity(SecCSFlags flags)
{
	if (this->isRoot()) {
		// the root-of-trust is valid by definition
		CODESIGN_EVAL_DYNAMIC_ROOT();
		return;
	}
	DTRACK(CODESIGN_EVAL_DYNAMIC, this, (char*)this->staticCode()->mainExecutablePath().c_str());
	
	//
	// Do not reorder the operations below without thorough cogitation. There are
	// interesting dependencies and significant performance issues. There is also
	// client code that relies on errors being noticed in a particular order.
	//
	// For the most part, failure of (reliable) identity will cause exceptions to be
	// thrown, and success is indicated by survival. If you make it to the end,
	// you have won the validity race. (Good rat.)
	//

	// check my host first, recursively
	this->host()->checkValidity(flags);

	SecStaticCode *myDisk = this->staticCode();
	SecStaticCode *hostDisk = this->host()->staticCode();

	// check my static state
	myDisk->validateDirectory();

	// check my own dynamic state
	if (!(this->host()->getGuestStatus(this) & kSecCodeStatusValid))
		MacOSError::throwMe(errSecCSGuestInvalid);
	
	// check that static and dynamic views are consistent
	if (this->cdHash() && !CFEqual(this->cdHash(), myDisk->cdHash()))
		MacOSError::throwMe(errSecCSStaticCodeChanged);

	// check host/guest constraints
	if (!this->host()->isRoot()) {	// not hosted by root of trust
		myDisk->validateRequirements(kSecHostRequirementType, hostDisk, errSecCSHostReject);
		hostDisk->validateRequirements(kSecGuestRequirementType, myDisk);
	}
}


//
// By default, we track no validity for guests (we don't have any)
//
uint32_t SecCode::getGuestStatus(SecCode *guest)
{
	MacOSError::throwMe(errSecCSNoSuchCode);
}

void SecCode::changeGuestStatus(SecCode *guest, SecCodeStatusOperation operation, CFDictionaryRef arguments)
{
	MacOSError::throwMe(errSecCSNoSuchCode);
}


//
// Given a bag of attribute values, automagically come up with a SecCode
// without any other information.
// This is meant to be the "just do what makes sense" generic call, for callers
// who don't want to engage in the fascinating dance of manual guest enumeration.
//
// Note that we expect the logic embedded here to change over time (in backward
// compatible fashion, one hopes), and that it's all right to use heuristics here
// as long as it's done sensibly.
//
// Be warned that the present logic is quite a bit ad-hoc, and will likely not
// handle arbitrary combinations of proxy hosting, dynamic hosting, and dedicated
// hosting all that well.
//
SecCode *SecCode::autoLocateGuest(CFDictionaryRef attributes, SecCSFlags flags)
{
	// special case: with no attributes at all, return the root of trust
	if (CFDictionaryGetCount(attributes) == 0)
		return KernelCode::active()->retain();
	
	// main logic: we need a pid, and we'll take a canonical guest id as an option
	int pid = 0;
	if (!cfscan(attributes, "{%O=%d}", kSecGuestAttributePid, &pid))
		CSError::throwMe(errSecCSUnsupportedGuestAttributes, kSecCFErrorGuestAttributes, attributes);
	if (SecCode *process =
			KernelCode::active()->locateGuest(CFTemp<CFDictionaryRef>("{%O=%d}", kSecGuestAttributePid, pid))) {
		SecPointer<SecCode> code;
		code.take(process);		// locateGuest gave us a retained object
		if (code->staticCode()->flag(kSecCodeSignatureHost)) {
			// might be a code host. Let's find out
			CFRef<CFMutableDictionaryRef> rest = makeCFMutableDictionary(attributes);
			CFDictionaryRemoveValue(rest, kSecGuestAttributePid);
			if (SecCode *guest = code->locateGuest(rest))
				return guest;
		}
		if (!CFDictionaryGetValue(attributes, kSecGuestAttributeCanonical)) {
			// only "soft" attributes, and no hosting is happening. Return the (non-)host itself
			return code.yield();
		}
	}
	MacOSError::throwMe(errSecCSNoSuchCode);
}


} // end namespace CodeSigning
} // end namespace Security