1/*
2 * Copyright (c) 2003-2009,2012 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// codesigdb - code-hash equivalence database
27//
28#include "codesigdb.h"
29#include "process.h"
30#include "server.h"
31#include "agentquery.h"
32#include <security_utilities/memutils.h>
33#include <security_utilities/logging.h>
34#include <Security/SecRequirementPriv.h>
35
36
37//
38// A self-constructing database key class.
39// Key format is <t><uid|S><key data>
40//  where
41// <t> single ASCII character type code ('H' for hash links)
42// <uid|S> decimal userid of owning user, or 'S' for system entries. Followed by null byte.
43// <key data> variable length key value (binary).
44//
45class DbKey : public CssmAutoData {
46public:
47	DbKey(char type, const CssmData &key, bool perUser = false, uid_t user = 0);
48};
49
50DbKey::DbKey(char type, const CssmData &key, bool perUser, uid_t user)
51	: CssmAutoData(Allocator::standard())
52{
53	using namespace LowLevelMemoryUtilities;
54	char header[20];
55	size_t headerLength;
56	if (perUser)
57		headerLength = 1 + sprintf(header, "%c%d", type, user);
58	else
59		headerLength = 1 + sprintf(header, "%cS", type);
60	malloc(headerLength + key.length());
61	memcpy(this->data(), header, headerLength);
62	memcpy(get().at(headerLength), key.data(), key.length());
63}
64
65
66//
67// A subclass of Identity made of whole cloth (from a raw CodeSignature ACL information)
68//
69struct AclIdentity : public CodeSignatures::Identity {
70	AclIdentity(const CssmData hash, string path) : mHash(hash), mPath(path) { }
71
72	string getPath() const { return mPath; }
73	const CssmData getHash() const { return mHash; }
74
75private:
76	const CssmData mHash;
77	const string mPath;
78};
79
80
81//
82// Construct a CodeSignatures objects
83//
84CodeSignatures::CodeSignatures(const char *path)
85{
86	try {
87		mDb.open(path, O_RDWR | O_CREAT, 0644);
88	} catch (const CommonError &err) {
89		try {
90			mDb.open(path, O_RDONLY, 0644);
91			Syslog::warning("database %s opened READONLY (R/W failed errno=%d)", path, err.unixError());
92			secdebug("codesign", "database %s opened READONLY (R/W failed errno=%d)", path, err.unixError());
93		} catch (...) {
94			Syslog::warning("cannot open %s; using no code equivalents", path);
95			secdebug("codesign", "unable to open %s; using no code equivalents", path);
96		}
97	}
98	if (mDb)
99		mDb.flush();	// in case we just created it
100	IFDUMPING("equiv", debugDump("open"));
101}
102
103CodeSignatures::~CodeSignatures()
104{
105}
106
107
108//
109// (Re)open the equivalence database.
110// This is useful to switch to database in another volume.
111//
112void CodeSignatures::open(const char *path)
113{
114	mDb.open(path, O_RDWR | O_CREAT, 0644);
115	mDb.flush();
116	IFDUMPING("equiv", debugDump("reopen"));
117}
118
119
120//
121// Basic Identity objects
122//
123CodeSignatures::Identity::Identity() : mState(untried)
124{ }
125
126CodeSignatures::Identity::~Identity()
127{ }
128
129string CodeSignatures::Identity::canonicalName(const string &path)
130{
131	string::size_type slash = path.rfind('/');
132	if (slash == string::npos)	// bloody unlikely, but whatever...
133		return path;
134	return path.substr(slash+1);
135}
136
137
138//
139// Find and store database objects (primitive layer)
140//
141bool CodeSignatures::find(Identity &id, uid_t user)
142{
143	if (id.mState != Identity::untried)
144		return id.mState == Identity::valid;
145	try {
146		DbKey userKey('H', id.getHash(), true, user);
147		CssmData linkValue;
148		if (mDb.get(userKey, linkValue)) {
149			id.mName = string(linkValue.interpretedAs<const char>(), linkValue.length());
150			IFDUMPING("equiv", id.debugDump("found/user"));
151			id.mState = Identity::valid;
152			return true;
153		}
154		DbKey sysKey('H', id.getHash());
155		if (mDb.get(sysKey, linkValue)) {
156			id.mName = string(linkValue.interpretedAs<const char>(), linkValue.length());
157			IFDUMPING("equiv", id.debugDump("found/system"));
158			id.mState = Identity::valid;
159			return true;
160		}
161	} catch (...) {
162		secdebug("codesign", "exception validating identity for %s - marking failed", id.path().c_str());
163		id.mState = Identity::invalid;
164	}
165	return id.mState == Identity::valid;
166}
167
168void CodeSignatures::makeLink(Identity &id, const string &ident, bool forUser, uid_t user)
169{
170	DbKey key('H', id.getHash(), forUser, user);
171	if (!mDb.put(key, StringData(ident)))
172		UnixError::throwMe();
173}
174
175
176//
177// Administrative manipulation calls
178//
179void CodeSignatures::addLink(const CssmData &oldHash, const CssmData &newHash,
180	const char *inName, bool forSystem)
181{
182	string name = Identity::canonicalName(inName);
183	uid_t user = Server::process().uid();
184	if (forSystem && user)	// only root user can establish forSystem links
185		UnixError::throwMe(EACCES);
186	if (!forSystem)	// in fact, for now we don't allow per-user calls at all
187		UnixError::throwMe(EACCES);
188	AclIdentity oldCode(oldHash, name);
189	AclIdentity newCode(newHash, name);
190	secdebug("codesign", "addlink for name %s", name.c_str());
191	StLock<Mutex> _(mDatabaseLock);
192	if (oldCode) {
193		if (oldCode.trustedName() != name) {
194			secdebug("codesign", "addlink does not match existing name %s",
195				oldCode.trustedName().c_str());
196			MacOSError::throwMe(CSSMERR_CSP_VERIFY_FAILED);
197		}
198	} else {
199		makeLink(oldCode, name, !forSystem, user);
200	}
201	if (!newCode)
202		makeLink(newCode, name, !forSystem, user);
203	mDb.flush();
204}
205
206void CodeSignatures::removeLink(const CssmData &hash, const char *name, bool forSystem)
207{
208	AclIdentity code(hash, name);
209	uid_t user = Server::process().uid();
210	if (forSystem && user)	// only root user can remove forSystem links
211		UnixError::throwMe(EACCES);
212	DbKey key('H', hash, !forSystem, user);
213	StLock<Mutex> _(mDatabaseLock);
214	mDb.erase(key);
215	mDb.flush();
216}
217
218
219//
220// Verify signature matches.
221// This ends up getting called when a CodeSignatureAclSubject is validated.
222// The OSXVerifier describes what we require of the client code; the process represents
223// the requesting client; and the context gives us access to the ACL and its environment
224// in case we want to, well, creatively rewrite it for some reason.
225//
226bool CodeSignatures::verify(Process &process,
227	const OSXVerifier &verifier, const AclValidationContext &context)
228{
229	secdebug("codesign", "start verify");
230
231	StLock<Mutex> _(process);
232	SecCodeRef code = process.currentGuest();
233	if (!code) {
234		secdebug("codesign", "no code base: fail");
235		return false;
236	}
237	if (SecRequirementRef requirement = verifier.requirement()) {
238		// If the ACL contains a code signature (requirement), we won't match against unsigned code at all.
239		// The legacy hash is ignored (it's for use by pre-Leopard systems).
240		secdebug("codesign", "CS requirement present; ignoring legacy hashes");
241		Server::active().longTermActivity();
242		switch (OSStatus rc = SecCodeCheckValidity(code, kSecCSDefaultFlags, requirement)) {
243		case noErr:
244			secdebug("codesign", "CS verify passed");
245			return true;
246		case errSecCSUnsigned:
247			secdebug("codesign", "CS verify against unsigned binary failed");
248			return false;
249		default:
250			secdebug("codesign", "CS verify failed OSStatus=%d", int32_t(rc));
251			return false;
252		}
253	}
254	switch (matchSignedClientToLegacyACL(process, code, verifier, context)) {
255	case noErr:						// handled, allow access
256		return true;
257	case errSecCSUnsigned:			// unsigned client, complete legacy case
258		secdebug("codesign", "no CS requirement - using legacy hash");
259		return verifyLegacy(process,
260			CssmData::wrap(verifier.legacyHash(), SHA1::digestLength),
261			verifier.path());
262	default:						// client unsuitable, reject this match
263		return false;
264	}
265}
266
267
268//
269// See if we can rewrite the ACL from legacy to Code Signing form without losing too much security.
270// Returns true if the present validation should succeed (we probably rewrote the ACL).
271// Returns false if the present validation shouldn't succeed based on what we did here (we may still
272// have rewritten the ACL, in principle).
273//
274// Note that these checks add nontrivial overhead to ACL processing. We want to eventually phase
275// this out, or at least make it an option that doesn't run all the time - perhaps an "extra legacy
276// effort" per-client mode bit.
277//
278static string trim(string s, char delimiter)
279{
280	string::size_type p = s.rfind(delimiter);
281	if (p != string::npos)
282		s = s.substr(p + 1);
283	return s;
284}
285
286static string trim(string s, char delimiter, string suffix)
287{
288	s = trim(s, delimiter);
289	int preLength = s.length() - suffix.length();
290	if (preLength > 0 && s.substr(preLength) == suffix)
291		s = s.substr(0, preLength);
292	return s;
293}
294
295OSStatus CodeSignatures::matchSignedClientToLegacyACL(Process &process,
296	SecCodeRef code, const OSXVerifier &verifier, const AclValidationContext &context)
297{
298	//
299	// Check whether we seem to be matching a legacy .Mac ACL against a member of the .Mac group
300	//
301	if (SecurityServerAcl::looksLikeLegacyDotMac(context)) {
302		Server::active().longTermActivity();
303		CFRef<SecRequirementRef> dotmac;
304		MacOSError::check(SecRequirementCreateGroup(CFSTR("dot-mac"), NULL, kSecCSDefaultFlags, &dotmac.aref()));
305		if (SecCodeCheckValidity(code, kSecCSDefaultFlags, dotmac) == noErr) {
306			secdebug("codesign", "client is a dot-mac application; update the ACL accordingly");
307
308			// create a suitable AclSubject (this is the above-the-API-line way)
309			CFRef<CFDataRef> reqdata;
310			MacOSError::check(SecRequirementCopyData(dotmac, kSecCSDefaultFlags, &reqdata.aref()));
311			RefPointer<CodeSignatureAclSubject> subject = new CodeSignatureAclSubject(NULL, "group://dot-mac");
312			subject->add((const BlobCore *)CFDataGetBytePtr(reqdata));
313
314			// add it to the ACL and pass the access check (we just quite literally did it above)
315			SecurityServerAcl::addToStandardACL(context, subject);
316			return noErr;
317		}
318	}
319
320	//
321	// Get best names for the ACL (legacy) subject and the (signed) client
322	//
323	CFRef<CFDictionaryRef> info;
324	MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref()));
325	CFStringRef signingIdentity = CFStringRef(CFDictionaryGetValue(info, kSecCodeInfoIdentifier));
326	if (!signingIdentity)		// unsigned
327		return errSecCSUnsigned;
328
329	string bundleName;	// client
330	if (CFDictionaryRef infoList = CFDictionaryRef(CFDictionaryGetValue(info, kSecCodeInfoPList)))
331		if (CFStringRef name = CFStringRef(CFDictionaryGetValue(infoList, kCFBundleNameKey)))
332			bundleName = trim(cfString(name), '.');
333	if (bundleName.empty())	// fall back to signing identifier
334		bundleName = trim(cfString(signingIdentity), '.');
335
336	string aclName = trim(verifier.path(), '/', ".app");	// ACL
337
338	secdebug("codesign", "matching signed client \"%s\" against legacy ACL \"%s\"",
339		bundleName.c_str(), aclName.c_str());
340
341	//
342	// Check whether we're matching a signed APPLE application against a legacy ACL by the same name
343	//
344	if (bundleName == aclName) {
345		const unsigned char reqData[] = {		// "anchor apple", version 1 blob, embedded here
346			0xfa, 0xde, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x10,
347			0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03
348		};
349		CFRef<SecRequirementRef> apple;
350		MacOSError::check(SecRequirementCreateWithData(CFTempData(reqData, sizeof(reqData)),
351			kSecCSDefaultFlags, &apple.aref()));
352		Server::active().longTermActivity();
353		switch (OSStatus rc = SecCodeCheckValidity(code, kSecCSDefaultFlags, apple)) {
354		case noErr:
355			{
356				secdebug("codesign", "withstands strict scrutiny; quietly adding new ACL");
357				RefPointer<OSXCode> wrap = new OSXCodeWrap(code);
358				RefPointer<AclSubject> subject = new CodeSignatureAclSubject(OSXVerifier(wrap));
359				SecurityServerAcl::addToStandardACL(context, subject);
360				return noErr;
361			}
362		default:
363			secdebug("codesign", "validation fails with rc=%d, rejecting", int32_t(rc));
364			return rc;
365		}
366		secdebug("codesign", "does not withstand strict scrutiny; ask the user");
367		QueryCodeCheck query;
368		query.inferHints(process);
369		if (!query(verifier.path().c_str())) {
370			secdebug("codesign", "user declined equivalence: cancel the access");
371			CssmError::throwMe(CSSM_ERRCODE_USER_CANCELED);
372		}
373		RefPointer<OSXCode> wrap = new OSXCodeWrap(code);
374		RefPointer<AclSubject> subject = new CodeSignatureAclSubject(OSXVerifier(wrap));
375		SecurityServerAcl::addToStandardACL(context, subject);
376		return noErr;
377	}
378
379	// not close enough to even ask - this can't match
380	return errSecCSReqFailed;
381}
382
383
384//
385// Perform legacy hash verification.
386// This is the pre-Leopard (Tiger, Panther) code path. Here we only have legacy hashes
387// (called, confusingly, "signatures"), which we're matching against suitably computed
388// "signatures" (hashes) on the requesting application. We consult the CodeEquivalenceDatabase
389// in a doomed attempt to track changes made to applications through updates, and issue
390// equivalence dialogs to users if we have a name match (but hash mismatch). That's all
391// there was before Code Signing; and that's what you'll continue to get if the requesting
392// application is unsigned. Until we throw the whole mess out altogether, hopefully by
393// the Next Big Cat After Leopard.
394//
395bool CodeSignatures::verifyLegacy(Process &process, const CssmData &signature, string path)
396{
397	// First of all, if the signature directly matches the client's code, we're obviously fine
398	// we don't even need the database for that...
399	Identity &clientIdentity = process;
400	try {
401		if (clientIdentity.getHash() == signature) {
402			secdebug("codesign", "direct match: pass");
403			return true;
404		}
405	} catch (...) {
406		secdebug("codesign", "exception getting client code hash: fail");
407		return false;
408	}
409
410#if CONSULT_LEGACY_CODE_EQUIVALENCE_DATABASE
411
412	// Ah well. Establish mediator objects for database signature links
413	AclIdentity aclIdentity(signature, path);
414
415	uid_t user = process.uid();
416	{
417		StLock<Mutex> _(mDatabaseLock);
418		find(aclIdentity, user);
419		find(clientIdentity, user);
420	}
421
422	// if both links exist, we can decide this right now
423	if (aclIdentity && clientIdentity) {
424		if (aclIdentity.trustedName() == clientIdentity.trustedName()) {
425			secdebug("codesign", "app references match: pass");
426			return true;
427		} else {
428			secdebug("codesign", "client/acl links exist but are unequal: fail");
429			return false;
430		}
431	}
432
433	// check for name equality
434	secdebug("codesign", "matching client %s against acl %s",
435		clientIdentity.name().c_str(), aclIdentity.name().c_str());
436	if (aclIdentity.name() != clientIdentity.name()) {
437		secdebug("codesign", "name/path mismatch: fail");
438		return false;
439	}
440
441	// The names match - we have a possible update.
442
443	// Take the UI lock now to serialize "update rushes".
444	LongtermStLock uiLocker(mUILock);
445
446	// re-read the database in case some other thread beat us to the update
447	{
448		StLock<Mutex> _(mDatabaseLock);
449		find(aclIdentity, user);
450		find(clientIdentity, user);
451	}
452	if (aclIdentity && clientIdentity) {
453		if (aclIdentity.trustedName() == clientIdentity.trustedName()) {
454			secdebug("codesign", "app references match: pass (on the rematch)");
455			return true;
456		} else {
457			secdebug("codesign", "client/acl links exist but are unequal: fail (on the rematch)");
458			return false;
459		}
460	}
461
462	// ask the user
463	QueryCodeCheck query;
464    query.inferHints(process);
465	if (!query(aclIdentity.path().c_str()))
466    {
467		secdebug("codesign", "user declined equivalence: cancel the access");
468		CssmError::throwMe(CSSM_ERRCODE_USER_CANCELED);
469	}
470
471	// take the database lock back for real
472	StLock<Mutex> _(mDatabaseLock);
473
474	// user wants us to go ahead and establish trust (if possible)
475	if (aclIdentity) {
476		// acl is linked but new client: link the client to this application
477		makeLink(clientIdentity, aclIdentity.trustedName(), true, user);
478		mDb.flush();
479		secdebug("codesign", "client %s linked to application %s: pass",
480			clientIdentity.path().c_str(), aclIdentity.trustedName().c_str());
481		return true;
482	}
483
484	if (clientIdentity) {	// code link exists, acl link missing
485		// client is linked but ACL (hash) never seen: link the ACL to this app
486		makeLink(aclIdentity, clientIdentity.trustedName(), true, user);
487		mDb.flush();
488		secdebug("codesign", "acl %s linked to client %s: pass",
489			aclIdentity.path().c_str(), clientIdentity.trustedName().c_str());
490		return true;
491	}
492
493	// the De Novo case: no links, must create everything
494	string ident = clientIdentity.name();
495	makeLink(clientIdentity, ident, true, user);
496	makeLink(aclIdentity, ident, true, user);
497	mDb.flush();
498	secdebug("codesign", "new linkages established: pass");
499	return true;
500
501#else /* ignore Code Equivalence Database */
502
503	return false;
504
505#endif
506}
507
508
509//
510// Debug dumping support
511//
512#if defined(DEBUGDUMP)
513
514void CodeSignatures::debugDump(const char *how) const
515{
516	using namespace Debug;
517	using namespace LowLevelMemoryUtilities;
518	if (!how)
519		how = "dump";
520	CssmData key, value;
521	if (!mDb.first(key, value)) {
522		dump("CODE EQUIVALENTS DATABASE IS EMPTY (%s)\n", how);
523	} else {
524		dump("CODE EQUIVALENTS DATABASE DUMP (%s)\n", how);
525		do {
526			const char *header = key.interpretedAs<const char>();
527			size_t headerLength = strlen(header) + 1;
528			dump("%s:", header);
529			dumpData(key.at(headerLength), key.length() - headerLength);
530			dump(" => ");
531			dumpData(value);
532			dump("\n");
533		} while (mDb.next(key, value));
534		dump("END DUMP\n");
535	}
536}
537
538void CodeSignatures::Identity::debugDump(const char *how) const
539{
540	using namespace Debug;
541	if (!how)
542		how = "dump";
543	dump("IDENTITY (%s) path=%s", how, getPath().c_str());
544	dump(" name=%s hash=", mName.empty() ? "(unset)" : mName.c_str());
545	dumpData(getHash());
546	dump("\n");
547}
548
549#endif //DEBUGDUMP
550