/ OSX / libsecurity_codesigning / lib / opaquewhitelist.cpp
opaquewhitelist.cpp
  1  /*
  2   * Copyright (c) 2014-2016 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  #include "opaquewhitelist.h"
 24  #include "csutilities.h"
 25  #include "StaticCode.h"
 26  #include <CoreFoundation/CoreFoundation.h>
 27  #include <Security/SecCodePriv.h>
 28  #include <Security/SecCodeSigner.h>
 29  #include <Security/SecStaticCode.h>
 30  #include <security_utilities/cfutilities.h>
 31  #include <security_utilities/cfmunge.h>
 32  #include <CoreFoundation/CFBundlePriv.h>
 33  #include <spawn.h>
 34  
 35  namespace Security {
 36  namespace CodeSigning {
 37  
 38  using namespace SQLite;
 39  
 40  
 41  static std::string hashString(CFDataRef hash);
 42  static void attachOpaque(SecStaticCodeRef code, SecAssessmentFeedback feedback);
 43  
 44  
 45  //
 46  // Open the database
 47  //
 48  OpaqueWhitelist::OpaqueWhitelist(const char *path, int flags)
 49  	: SQLite::Database(path ? path : opaqueDatabase, flags)
 50  {
 51  	SQLite::Statement createConditions(*this,
 52  		"CREATE TABLE IF NOT EXISTS conditions ("
 53  		" label text,"
 54  	   " weight real not null unique,"
 55  	   " source text,"
 56  	   " identifier text,"
 57  	   " version text,"
 58  	   " conditions text not null);"
 59  	);
 60  	createConditions.execute();
 61  	mOverrideQueue = dispatch_queue_create("com.apple.security.assessment.whitelist-override", DISPATCH_QUEUE_SERIAL);
 62  }
 63  
 64  OpaqueWhitelist::~OpaqueWhitelist()
 65  {
 66  	dispatch_release(mOverrideQueue);
 67  }
 68  
 69  
 70  //
 71  // Check if a code object is whitelisted
 72  //
 73  bool OpaqueWhitelist::contains(SecStaticCodeRef codeRef, SecAssessmentFeedback feedback, OSStatus reason)
 74  {
 75  	// make our own copy of the code object, so we can poke at it without disturbing the original
 76  	SecPointer<SecStaticCode> code = new SecStaticCode(SecStaticCode::requiredStatic(codeRef)->diskRep());
 77  
 78  	CFCopyRef<CFDataRef> current = code->cdHash();	// current cdhash
 79  	CFDataRef opaque = NULL;	// holds computed opaque cdhash
 80  	bool match = false; 	// holds final result
 81  
 82  	if (!current)
 83  		return false;	// unsigned
 84  
 85  	// collect auxiliary information for trace
 86  	CFRef<CFDictionaryRef> info;
 87  	std::string team = "";
 88  	CFStringRef cfVersion = NULL, cfShortVersion = NULL, cfExecutable = NULL;
 89  	if (errSecSuccess == SecCodeCopySigningInformation(code->handle(false), kSecCSSigningInformation, &info.aref())) {
 90  		if (CFStringRef cfTeam = CFStringRef(CFDictionaryGetValue(info, kSecCodeInfoTeamIdentifier)))
 91  			team = cfString(cfTeam);
 92  		if (CFDictionaryRef infoPlist = CFDictionaryRef(CFDictionaryGetValue(info, kSecCodeInfoPList))) {
 93  			if (CFTypeRef version = CFDictionaryGetValue(infoPlist, kCFBundleVersionKey))
 94  				if (CFGetTypeID(version) == CFStringGetTypeID())
 95  					cfVersion = CFStringRef(version);
 96  			if (CFTypeRef shortVersion = CFDictionaryGetValue(infoPlist, _kCFBundleShortVersionStringKey))
 97  				if (CFGetTypeID(shortVersion) == CFStringGetTypeID())
 98  					cfShortVersion = CFStringRef(shortVersion);
 99  			if (CFTypeRef executable = CFDictionaryGetValue(infoPlist, kCFBundleExecutableKey))
100  				if (CFGetTypeID(executable) == CFStringGetTypeID())
101  					cfExecutable = CFStringRef(executable);
102  		}
103  	}
104  
105  	// compute and attach opaque signature
106  	attachOpaque(code->handle(false), feedback);
107  	opaque = code->cdHash();
108  
109  	// lookup current cdhash in whitelist
110  	if (opaque) {
111  		SQLite::Statement lookup(*this, "SELECT opaque FROM whitelist WHERE current=:current"
112  			" AND opaque != 'disable override'");
113  		lookup.bind(":current") = current.get();
114  		while (lookup.nextRow()) {
115  			CFRef<CFDataRef> expected = lookup[0].data();
116  			if (CFEqual(opaque, expected)) {
117  				match = true;	// actual opaque cdhash matches expected
118  				break;
119  			}
120  		}
121  	}
122  
123  	// prepare strings for use inside block
124  	std::string currentHash = hashString(current);
125  	std::string opaqueHash = opaque ? hashString(opaque) : "none";
126  
127  	// send a trace indicating the result
128  	MessageTrace trace("com.apple.security.assessment.whitelist2", code->identifier().c_str());
129  	trace.add("signature2", "%s", currentHash.c_str());
130  	trace.add("signature3", "%s", opaqueHash.c_str());
131  	trace.add("result", match ? "pass" : "fail");
132  	trace.add("reason", "%d", (int)reason);
133  	if (!team.empty())
134  		trace.add("teamid", "%s", team.c_str());
135  	if (cfVersion)
136  		trace.add("version", "%s", cfString(cfVersion).c_str());
137  	if (cfShortVersion)
138  		trace.add("version2", "%s", cfString(cfShortVersion).c_str());
139  	if (cfExecutable)
140  		trace.add("execname", "%s", cfString(cfExecutable).c_str());
141  	trace.send("");
142  
143  	return match;
144  }
145  	
146  
147  //
148  // Obtain special validation conditions for a static code, based on database configuration.
149  //
150  CFDictionaryRef OpaqueWhitelist::validationConditionsFor(SecStaticCodeRef code)
151  {
152  	// figure out which team key to use
153  	std::string team = "UNKNOWN";
154  	CFStringRef cfId = NULL;
155  	CFStringRef cfVersion = NULL;
156  	CFRef<CFDictionaryRef> info;	// holds lifetimes for the above
157  	if (errSecSuccess == SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref())) {
158  		if (CFStringRef cfTeam = CFStringRef(CFDictionaryGetValue(info, kSecCodeInfoTeamIdentifier)))
159  			team = cfString(cfTeam);
160  		cfId = CFStringRef(CFDictionaryGetValue(info, kSecCodeInfoIdentifier));
161  		if (CFDictionaryRef infoPlist = CFDictionaryRef(CFDictionaryGetValue(info, kSecCodeInfoPList)))
162  			if (CFTypeRef version = CFDictionaryGetValue(infoPlist, _kCFBundleShortVersionStringKey))
163  				if (CFGetTypeID(version) == CFStringGetTypeID())
164  					cfVersion = CFStringRef(version);
165  	}
166  	if (cfId == NULL)	// unsigned; punt
167  		return NULL;
168  	
169  	// find the highest weight matching condition. We perform no merging and the heaviest rule wins
170  	SQLite::Statement matches(*this,
171  		"SELECT conditions FROM conditions"
172  		" WHERE (source = :source or source IS NULL)"
173  		" AND (identifier = :identifier or identifier is NULL)"
174  		" AND ((:version IS NULL AND version IS NULL) OR (version = :version OR version IS NULL))"
175  		" ORDER BY weight DESC"
176  		" LIMIT 1"
177  	);
178  	matches.bind(":source") = team;
179  	matches.bind(":identifier") = cfString(cfId);
180  	if (cfVersion)
181  		matches.bind(":version") = cfString(cfVersion);
182  	if (matches.nextRow()) {
183  		CFTemp<CFDictionaryRef> conditions((const char*)matches[0]);
184  		return conditions.yield();
185  	}
186  	// no matches
187  	return NULL;
188  }
189  
190  
191  //
192  // Convert a SHA1 hash to a hex string
193  //
194  static std::string hashString(CFDataRef hash)
195  {
196  	if (CFDataGetLength(hash) != sizeof(SHA1::Digest)) {
197  		return std::string();
198  	} else {
199  		const UInt8 *bytes = CFDataGetBytePtr(hash);
200  		char s[2 * SHA1::digestLength + 1];
201  		for (unsigned n = 0; n < SHA1::digestLength; n++)
202  			sprintf(&s[2*n], "%2.2x", bytes[n]);
203  		return std::string(s);
204  	}
205  }
206  
207  
208  //
209  // Add a code object to the whitelist
210  //
211  void OpaqueWhitelist::add(SecStaticCodeRef codeRef)
212  {
213  	// make our own copy of the code object
214  	SecPointer<SecStaticCode> code = new SecStaticCode(SecStaticCode::requiredStatic(codeRef)->diskRep());
215  
216  	CFCopyRef<CFDataRef> current = code->cdHash();
217  	attachOpaque(code->handle(false), NULL);	// compute and attach an opaque signature
218  	CFDataRef opaque = code->cdHash();
219  
220  	SQLite::Statement insert(*this, "INSERT OR REPLACE INTO whitelist (current,opaque) VALUES (:current, :opaque)");
221  	insert.bind(":current") = current.get();
222  	insert.bind(":opaque") = opaque;
223  	insert.execute();
224  }
225  
226  
227  //
228  // Generate and attach an ad-hoc opaque signature
229  // Use SHA-1 digests because that's what the whitelist is made with
230  //
231  static void attachOpaque(SecStaticCodeRef code, SecAssessmentFeedback feedback)
232  {
233  	CFTemp<CFDictionaryRef> rules("{"	// same resource rules as used for collection
234  		"rules={"
235  			"'^.*' = #T"
236  			"'^Info\\.plist$' = {omit=#T,weight=10}"
237  		"},rules2={"
238  			"'^(Frameworks|SharedFrameworks|Plugins|Plug-ins|XPCServices|Helpers|MacOS)/' = {nested=#T, weight=0}" 
239  			"'^.*' = #T"
240  			"'^Info\\.plist$' = {omit=#T,weight=10}"
241  			"'^[^/]+$' = {top=#T, weight=0}"
242  		"}"
243  	"}");
244  
245  	CFRef<CFDataRef> signature = CFDataCreateMutable(NULL, 0);
246  	CFTemp<CFDictionaryRef> arguments("{%O=%O, %O=#N, %O=%d, %O=%O}",
247  		kSecCodeSignerDetached, signature.get(),
248  		kSecCodeSignerIdentity, /* kCFNull, */
249  		kSecCodeSignerDigestAlgorithm, kSecCodeSignatureHashSHA1,
250  		kSecCodeSignerResourceRules, rules.get());
251  	CFRef<SecCodeSignerRef> signer;
252  	SecCSFlags creationFlags = kSecCSSignOpaque | kSecCSSignNoV1 | kSecCSSignBundleRoot;
253  	SecCSFlags operationFlags = 0;
254  
255  	if (feedback)
256  		operationFlags |= kSecCSReportProgress;
257  	MacOSError::check(SecStaticCodeSetCallback(code, kSecCSDefaultFlags, NULL, ^CFTypeRef(SecStaticCodeRef code, CFStringRef stage, CFDictionaryRef info) {
258  		if (CFEqual(stage, CFSTR("progress"))) {
259  			bool proceed = feedback(kSecAssessmentFeedbackProgress, info);
260  			if (!proceed)
261  				SecStaticCodeCancelValidation(code, kSecCSDefaultFlags);
262  		}
263  		return NULL;
264  	}));
265  	
266  	MacOSError::check(SecCodeSignerCreate(arguments, creationFlags, &signer.aref()));
267  	MacOSError::check(SecCodeSignerAddSignature(signer, code, operationFlags));
268  	MacOSError::check(SecCodeSetDetachedSignature(code, signature, kSecCSDefaultFlags));
269  }
270  
271  
272  } // end namespace CodeSigning
273  } // end namespace Security