]> git.saurik.com Git - apple/security.git/blob - OSX/libsecurity_codesigning/lib/policyengine.cpp
Security-57337.50.23.tar.gz
[apple/security.git] / OSX / libsecurity_codesigning / lib / policyengine.cpp
1 /*
2 * Copyright (c) 2011-2014 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 "policyengine.h"
24 #include "xar++.h"
25 #include "quarantine++.h"
26 #include "codesigning_dtrace.h"
27 #include <security_utilities/cfmunge.h>
28 #include <Security/Security.h>
29 #include <Security/SecCodePriv.h>
30 #include <Security/SecRequirementPriv.h>
31 #include <Security/SecPolicyPriv.h>
32 #include <Security/SecTrustPriv.h>
33 #include <Security/SecCodeSigner.h>
34 #include <Security/cssmapplePriv.h>
35 #include <security_utilities/unix++.h>
36 #include <notify.h>
37
38 #include "diskrep.h"
39 #include "codedirectory.h"
40 #include "csutilities.h"
41 #include "StaticCode.h"
42
43 #include <CoreServices/CoreServicesPriv.h>
44 #include "SecCodePriv.h"
45 #undef check // Macro! Yech.
46
47 extern "C" {
48 #include <OpenScriptingUtilPriv.h>
49 }
50
51
52 namespace Security {
53 namespace CodeSigning {
54
55 static const double NEGATIVE_HOLD = 60.0/86400; // 60 seconds to cache negative outcomes
56
57 static const char RECORDER_DIR[] = "/tmp/gke-"; // recorder mode destination for detached signatures
58 enum {
59 recorder_code_untrusted = 0, // signed but untrusted
60 recorder_code_adhoc = 1, // unsigned; signature recorded
61 recorder_code_unable = 2, // unsigned; unable to record signature
62 };
63
64
65 static void authorizeUpdate(SecAssessmentFlags flags, CFDictionaryRef context);
66 static bool codeInvalidityExceptions(SecStaticCodeRef code, CFMutableDictionaryRef result);
67 static CFTypeRef installerPolicy() CF_RETURNS_RETAINED;
68
69
70 //
71 // Core structure
72 //
73 PolicyEngine::PolicyEngine()
74 : PolicyDatabase(NULL, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE)
75 {
76 }
77
78 PolicyEngine::~PolicyEngine()
79 { }
80
81
82 //
83 // Top-level evaluation driver
84 //
85 void PolicyEngine::evaluate(CFURLRef path, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result)
86 {
87 // update GKE
88 installExplicitSet(gkeAuthFile, gkeSigsFile);
89
90 // find the global evaluation manager
91 EvaluationManager *evaluationManager = EvaluationManager::globalManager();
92
93 // perform the evaluation
94 EvaluationTask *evaluationTask = evaluationManager->evaluationTask(this, path, type, flags, context, result);
95 evaluationManager->finalizeTask(evaluationTask, flags, result);
96
97 // if rejected, reset the automatic rearm timer
98 if (CFDictionaryGetValue(result, kSecAssessmentAssessmentVerdict) == kCFBooleanFalse)
99 resetRearmTimer("reject");
100 }
101
102
103 static std::string createWhitelistScreen(char type, const Byte *digest, size_t length)
104 {
105 char buffer[2*length + 2];
106 buffer[0] = type;
107 for (size_t n = 0; n < length; n++)
108 sprintf(buffer + 1 + 2*n, "%02.2x", digest[n]);
109 return buffer;
110 }
111
112
113 void PolicyEngine::evaluateCodeItem(SecStaticCodeRef code, CFURLRef path, AuthorityType type, SecAssessmentFlags flags, bool nested, CFMutableDictionaryRef result)
114 {
115
116 SQLite::Statement query(*this,
117 "SELECT allow, requirement, id, label, expires, flags, disabled, filter_unsigned, remarks FROM scan_authority"
118 " WHERE type = :type"
119 " ORDER BY priority DESC;");
120 query.bind(":type").integer(type);
121
122 SQLite3::int64 latentID = 0; // first (highest priority) disabled matching ID
123 std::string latentLabel; // ... and associated label, if any
124
125 while (query.nextRow()) {
126 bool allow = int(query[0]);
127 const char *reqString = query[1];
128 SQLite3::int64 id = query[2];
129 const char *label = query[3];
130 double expires = query[4];
131 sqlite3_int64 ruleFlags = query[5];
132 SQLite3::int64 disabled = query[6];
133 // const char *filter = query[7];
134 // const char *remarks = query[8];
135
136 CFRef<SecRequirementRef> requirement;
137 MacOSError::check(SecRequirementCreateWithString(CFTempString(reqString), kSecCSDefaultFlags, &requirement.aref()));
138 switch (OSStatus rc = SecStaticCodeCheckValidity(code, kSecCSBasicValidateOnly | kSecCSCheckGatekeeperArchitectures, requirement)) {
139 case errSecSuccess:
140 break; // rule match; process below
141 case errSecCSReqFailed:
142 continue; // rule does not apply
143 case errSecCSVetoed:
144 return; // nested code has failed to pass
145 default:
146 MacOSError::throwMe(rc); // general error; pass to caller
147 }
148
149 // if this rule is disabled, skip it but record the first matching one for posterity
150 if (disabled && latentID == 0) {
151 latentID = id;
152 latentLabel = label ? label : "";
153 continue;
154 }
155
156 // current rule is first rule (in priority order) that matched. Apply it
157 if (nested) // success, nothing to record
158 return;
159
160 CFRef<CFDictionaryRef> info; // as needed
161 if (flags & kSecAssessmentFlagRequestOrigin) {
162 if (!info)
163 MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref()));
164 if (CFArrayRef chain = CFArrayRef(CFDictionaryGetValue(info, kSecCodeInfoCertificates)))
165 setOrigin(chain, result);
166 }
167 if (!(ruleFlags & kAuthorityFlagInhibitCache) && !(flags & kSecAssessmentFlagNoCache)) { // cache inhibit
168 if (!info)
169 MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref()));
170 if (SecTrustRef trust = SecTrustRef(CFDictionaryGetValue(info, kSecCodeInfoTrust))) {
171 CFRef<CFDictionaryRef> xinfo;
172 MacOSError::check(SecTrustCopyExtendedResult(trust, &xinfo.aref()));
173 if (CFDateRef limit = CFDateRef(CFDictionaryGetValue(xinfo, kSecTrustExpirationDate))) {
174 this->recordOutcome(code, allow, type, min(expires, dateToJulian(limit)), id);
175 }
176 }
177 }
178 if (allow) {
179 if (SYSPOLICY_ASSESS_OUTCOME_ACCEPT_ENABLED()) {
180 if (!info)
181 MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref()));
182 CFDataRef cdhash = CFDataRef(CFDictionaryGetValue(info, kSecCodeInfoUnique));
183 SYSPOLICY_ASSESS_OUTCOME_ACCEPT(cfString(path).c_str(), type, label, cdhash ? CFDataGetBytePtr(cdhash) : NULL);
184 }
185 } else {
186 if (SYSPOLICY_ASSESS_OUTCOME_DENY_ENABLED() || SYSPOLICY_RECORDER_MODE_ENABLED()) {
187 if (!info)
188 MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref()));
189 CFDataRef cdhash = CFDataRef(CFDictionaryGetValue(info, kSecCodeInfoUnique));
190 std::string cpath = cfString(path);
191 const void *hashp = cdhash ? CFDataGetBytePtr(cdhash) : NULL;
192 SYSPOLICY_ASSESS_OUTCOME_DENY(cpath.c_str(), type, label, hashp);
193 SYSPOLICY_RECORDER_MODE(cpath.c_str(), type, label, hashp, recorder_code_untrusted);
194 }
195 }
196 cfadd(result, "{%O=%B}", kSecAssessmentAssessmentVerdict, allow);
197 addAuthority(flags, result, label, id);
198 return;
199 }
200
201 // no applicable authority (but signed, perhaps temporarily). Deny by default
202 CFRef<CFDictionaryRef> info;
203 MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref()));
204 if (flags & kSecAssessmentFlagRequestOrigin) {
205 if (CFArrayRef chain = CFArrayRef(CFDictionaryGetValue(info, kSecCodeInfoCertificates)))
206 setOrigin(chain, result);
207 }
208 if (SYSPOLICY_ASSESS_OUTCOME_DEFAULT_ENABLED() || SYSPOLICY_RECORDER_MODE_ENABLED()) {
209 CFDataRef cdhash = CFDataRef(CFDictionaryGetValue(info, kSecCodeInfoUnique));
210 const void *hashp = cdhash ? CFDataGetBytePtr(cdhash) : NULL;
211 std::string cpath = cfString(path);
212 SYSPOLICY_ASSESS_OUTCOME_DEFAULT(cpath.c_str(), type, latentLabel.c_str(), hashp);
213 SYSPOLICY_RECORDER_MODE(cpath.c_str(), type, latentLabel.c_str(), hashp, 0);
214 }
215 if (!(flags & kSecAssessmentFlagNoCache))
216 this->recordOutcome(code, false, type, this->julianNow() + NEGATIVE_HOLD, latentID);
217 cfadd(result, "{%O=%B}", kSecAssessmentAssessmentVerdict, false);
218 addAuthority(flags, result, latentLabel.c_str(), latentID);
219 }
220
221
222 void PolicyEngine::adjustValidation(SecStaticCodeRef code)
223 {
224 CFRef<CFDictionaryRef> conditions = mOpaqueWhitelist.validationConditionsFor(code);
225 SecStaticCodeSetValidationConditions(code, conditions);
226 }
227
228
229 bool PolicyEngine::temporarySigning(SecStaticCodeRef code, AuthorityType type, CFURLRef path, SecAssessmentFlags matchFlags)
230 {
231 if (matchFlags == 0) { // playback; consult authority table for matches
232 DiskRep *rep = SecStaticCode::requiredStatic(code)->diskRep();
233 std::string screen;
234 if (CFRef<CFDataRef> info = rep->component(cdInfoSlot)) {
235 SHA1 hash;
236 hash.update(CFDataGetBytePtr(info), CFDataGetLength(info));
237 SHA1::Digest digest;
238 hash.finish(digest);
239 screen = createWhitelistScreen('I', digest, sizeof(digest));
240 } else if (CFRef<CFDataRef> repSpecific = rep->component(cdRepSpecificSlot)) {
241 // got invented after SHA-1 deprecation, so we'll use SHA256, which is the new default
242 CCHashInstance hash(kCCDigestSHA256);
243 hash.update(CFDataGetBytePtr(repSpecific), CFDataGetLength(repSpecific));
244 Byte digest[256/8];
245 hash.finish(digest);
246 screen = createWhitelistScreen('R', digest, sizeof(digest));
247 } else if (rep->mainExecutableImage()) {
248 screen = "N";
249 } else {
250 SHA1 hash;
251 hashFileData(rep->mainExecutablePath().c_str(), &hash);
252 SHA1::Digest digest;
253 hash.finish(digest);
254 screen = createWhitelistScreen('M', digest, sizeof(digest));
255 }
256 SQLite::Statement query(*this,
257 "SELECT flags FROM authority "
258 "WHERE type = :type"
259 " AND NOT flags & :flag"
260 " AND CASE WHEN filter_unsigned IS NULL THEN remarks = :remarks ELSE filter_unsigned = :screen END");
261 query.bind(":type").integer(type);
262 query.bind(":flag").integer(kAuthorityFlagDefault);
263 query.bind(":screen") = screen;
264 query.bind(":remarks") = cfString(path);
265 if (!query.nextRow()) // guaranteed no matching rule
266 return false;
267 matchFlags = SQLite3::int64(query[0]);
268 }
269
270 try {
271 // ad-hoc sign the code and attach the signature
272 CFRef<CFDataRef> signature = CFDataCreateMutable(NULL, 0);
273 CFTemp<CFMutableDictionaryRef> arguments("{%O=%O, %O=#N, %O=%d}", kSecCodeSignerDetached, signature.get(), kSecCodeSignerIdentity,
274 kSecCodeSignerDigestAlgorithm, (matchFlags & kAuthorityFlagWhitelistSHA256) ? kSecCodeSignatureHashSHA256 : kSecCodeSignatureHashSHA1);
275 CFRef<SecCodeSignerRef> signer;
276 MacOSError::check(SecCodeSignerCreate(arguments, (matchFlags & kAuthorityFlagWhitelistV2) ? kSecCSSignOpaque : kSecCSSignV1, &signer.aref()));
277 MacOSError::check(SecCodeSignerAddSignature(signer, code, kSecCSDefaultFlags));
278 MacOSError::check(SecCodeSetDetachedSignature(code, signature, kSecCSDefaultFlags));
279
280 SecRequirementRef dr = NULL;
281 SecCodeCopyDesignatedRequirement(code, kSecCSDefaultFlags, &dr);
282 CFStringRef drs = NULL;
283 SecRequirementCopyString(dr, kSecCSDefaultFlags, &drs);
284
285 // if we're in GKE recording mode, save that signature and report its location
286 if (SYSPOLICY_RECORDER_MODE_ENABLED()) {
287 int status = recorder_code_unable; // ephemeral signature (not recorded)
288 if (geteuid() == 0) {
289 CFRef<CFUUIDRef> uuid = CFUUIDCreate(NULL);
290 std::string sigfile = RECORDER_DIR + cfStringRelease(CFUUIDCreateString(NULL, uuid)) + ".tsig";
291 try {
292 UnixPlusPlus::AutoFileDesc fd(sigfile, O_WRONLY | O_CREAT);
293 fd.write(CFDataGetBytePtr(signature), CFDataGetLength(signature));
294 status = recorder_code_adhoc; // recorded signature
295 SYSPOLICY_RECORDER_MODE_ADHOC_PATH(cfString(path).c_str(), type, sigfile.c_str());
296 } catch (...) { }
297 }
298
299 // now report the D probe itself
300 CFRef<CFDictionaryRef> info;
301 MacOSError::check(SecCodeCopySigningInformation(code, kSecCSDefaultFlags, &info.aref()));
302 CFDataRef cdhash = CFDataRef(CFDictionaryGetValue(info, kSecCodeInfoUnique));
303 SYSPOLICY_RECORDER_MODE(cfString(path).c_str(), type, "",
304 cdhash ? CFDataGetBytePtr(cdhash) : NULL, status);
305 }
306
307 return true; // it worked; we're now (well) signed
308 } catch (...) { }
309
310 return false;
311 }
312
313
314 //
315 // Executable code.
316 // Read from disk, evaluate properly, cache as indicated.
317 //
318 void PolicyEngine::evaluateCode(CFURLRef path, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result, bool handleUnsigned)
319 {
320 // not really a Gatekeeper function... but reject all "hard quarantined" files because they were made from sandboxed sources without download privilege
321 FileQuarantine qtn(cfString(path).c_str());
322 if (qtn.flag(QTN_FLAG_HARD))
323 MacOSError::throwMe(errSecCSFileHardQuarantined);
324
325 // hack: if caller passed a UTI, use that to turn off app-only checks for some well-known ones
326 bool appOk = false;
327 if (CFStringRef uti = CFStringRef(CFDictionaryGetValue(context, kSecAssessmentContextKeyUTI))) {
328 appOk = CFEqual(uti, CFSTR("com.apple.systempreference.prefpane"))
329 || CFEqual(uti, CFSTR("com.apple.systempreference.screen-saver"))
330 || CFEqual(uti, CFSTR("com.apple.systempreference.screen-slide-saver"))
331 || CFEqual(uti, CFSTR("com.apple.menu-extra"));
332 }
333
334 CFCopyRef<SecStaticCodeRef> code;
335 MacOSError::check(SecStaticCodeCreateWithPath(path, kSecCSDefaultFlags, &code.aref()));
336
337 SecCSFlags validationFlags = kSecCSEnforceRevocationChecks | kSecCSCheckAllArchitectures;
338 if (!(flags & kSecAssessmentFlagAllowWeak))
339 validationFlags |= kSecCSStrictValidate;
340 adjustValidation(code);
341
342 // deal with a very special case (broken 10.6/10.7 Applet bundles)
343 OSStatus rc = SecStaticCodeCheckValidity(code, validationFlags | kSecCSBasicValidateOnly, NULL);
344 if (rc == errSecCSSignatureFailed) {
345 if (!codeInvalidityExceptions(code, result)) { // invalidly signed, no exceptions -> error
346 if (SYSPOLICY_ASSESS_OUTCOME_BROKEN_ENABLED())
347 SYSPOLICY_ASSESS_OUTCOME_BROKEN(cfString(path).c_str(), type, false);
348 MacOSError::throwMe(rc);
349 }
350 // recognized exception - treat as unsigned
351 if (SYSPOLICY_ASSESS_OUTCOME_BROKEN_ENABLED())
352 SYSPOLICY_ASSESS_OUTCOME_BROKEN(cfString(path).c_str(), type, true);
353 rc = errSecCSUnsigned;
354 }
355
356 // ad-hoc sign unsigned code
357 if (rc == errSecCSUnsigned && handleUnsigned && (!overrideAssessment(flags) || SYSPOLICY_RECORDER_MODE_ENABLED())) {
358 if (temporarySigning(code, type, path, 0)) {
359 rc = errSecSuccess; // clear unsigned; we are now well-signed
360 validationFlags |= kSecCSBasicValidateOnly; // no need to re-validate deep contents
361 }
362 }
363
364 // prepare for deep traversal of (hopefully) good signatures
365 SecAssessmentFeedback feedback = SecAssessmentFeedback(CFDictionaryGetValue(context, kSecAssessmentContextKeyFeedback));
366 MacOSError::check(SecStaticCodeSetCallback(code, kSecCSDefaultFlags, NULL, ^CFTypeRef (SecStaticCodeRef item, CFStringRef cfStage, CFDictionaryRef info) {
367 string stage = cfString(cfStage);
368 if (stage == "prepared") {
369 if (!CFEqual(item, code)) // genuine nested (not top) code
370 adjustValidation(item);
371 } else if (stage == "progress") {
372 if (feedback && CFEqual(item, code)) { // top level progress
373 bool proceed = feedback(kSecAssessmentFeedbackProgress, info);
374 if (!proceed)
375 SecStaticCodeCancelValidation(code, kSecCSDefaultFlags);
376 }
377 } else if (stage == "validated") {
378 SecStaticCodeSetCallback(item, kSecCSDefaultFlags, NULL, NULL); // clear callback to avoid unwanted recursion
379 evaluateCodeItem(item, path, type, flags, item != code, result);
380 if (CFTypeRef verdict = CFDictionaryGetValue(result, kSecAssessmentAssessmentVerdict))
381 if (CFEqual(verdict, kCFBooleanFalse))
382 return makeCFNumber(OSStatus(errSecCSVetoed)); // (signal nested-code policy failure, picked up below)
383 }
384 return NULL;
385 }));
386
387 // go for it!
388 SecCSFlags topFlags = validationFlags | kSecCSCheckNestedCode | kSecCSRestrictSymlinks | kSecCSReportProgress;
389 if (type == kAuthorityExecute && !appOk)
390 topFlags |= kSecCSRestrictToAppLike;
391 switch (rc = SecStaticCodeCheckValidity(code, topFlags, NULL)) {
392 case errSecSuccess: // continue below
393 break;
394 case errSecCSUnsigned:
395 cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict);
396 addAuthority(flags, result, "no usable signature");
397 return;
398 case errSecCSVetoed: // nested code rejected by rule book; result was filled out there
399 return;
400 case errSecCSWeakResourceRules:
401 case errSecCSWeakResourceEnvelope:
402 case errSecCSResourceNotSupported:
403 case errSecCSAmbiguousBundleFormat:
404 case errSecCSSignatureNotVerifiable:
405 case errSecCSRegularFile:
406 case errSecCSBadMainExecutable:
407 case errSecCSBadFrameworkVersion:
408 case errSecCSUnsealedAppRoot:
409 case errSecCSUnsealedFrameworkRoot:
410 case errSecCSInvalidSymlink:
411 case errSecCSNotAppLike:
412 {
413 // consult the whitelist
414 bool allow = false;
415 const char *label;
416 // we've bypassed evaluateCodeItem before we failed validation. Explicitly apply it now
417 SecStaticCodeSetCallback(code, kSecCSDefaultFlags, NULL, NULL);
418 evaluateCodeItem(code, path, type, flags | kSecAssessmentFlagNoCache, false, result);
419 if (CFTypeRef verdict = CFDictionaryGetValue(result, kSecAssessmentAssessmentVerdict)) {
420 // verdict rendered from a nested component - signature not acceptable to Gatekeeper
421 if (CFEqual(verdict, kCFBooleanFalse)) // nested code rejected by rule book; result was filled out there
422 return;
423 if (CFEqual(verdict, kCFBooleanTrue) && !(flags & kSecAssessmentFlagIgnoreWhitelist))
424 if (mOpaqueWhitelist.contains(code, feedback, rc))
425 allow = true;
426 }
427 if (allow) {
428 label = "allowed cdhash";
429 } else {
430 CFDictionaryReplaceValue(result, kSecAssessmentAssessmentVerdict, kCFBooleanFalse);
431 label = "obsolete resource envelope";
432 }
433 cfadd(result, "{%O=%d}", kSecAssessmentAssessmentCodeSigningError, rc);
434 addAuthority(flags, result, label, 0, NULL, true);
435 return;
436 }
437 default:
438 MacOSError::throwMe(rc);
439 }
440 }
441
442
443 //
444 // Installer archive.
445 // Hybrid policy: If we detect an installer signature, use and validate that.
446 // If we don't, check for a code signature instead.
447 //
448 void PolicyEngine::evaluateInstall(CFURLRef path, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result)
449 {
450 const AuthorityType type = kAuthorityInstall;
451
452 // check for recent explicit approval, using a bookmark's FileResourceIdentifierKey
453 if (CFRef<CFDataRef> bookmark = cfLoadFile(lastApprovedFile)) {
454 Boolean stale;
455 if (CFRef<CFURLRef> url = CFURLCreateByResolvingBookmarkData(NULL, bookmark,
456 kCFBookmarkResolutionWithoutUIMask | kCFBookmarkResolutionWithoutMountingMask, NULL, NULL, &stale, NULL))
457 if (CFRef<CFDataRef> savedIdent = CFDataRef(CFURLCreateResourcePropertyForKeyFromBookmarkData(NULL, kCFURLFileResourceIdentifierKey, bookmark)))
458 if (CFRef<CFDateRef> savedMod = CFDateRef(CFURLCreateResourcePropertyForKeyFromBookmarkData(NULL, kCFURLContentModificationDateKey, bookmark))) {
459 CFRef<CFDataRef> currentIdent;
460 CFRef<CFDateRef> currentMod;
461 if (CFURLCopyResourcePropertyForKey(path, kCFURLFileResourceIdentifierKey, &currentIdent.aref(), NULL))
462 if (CFURLCopyResourcePropertyForKey(path, kCFURLContentModificationDateKey, &currentMod.aref(), NULL))
463 if (CFEqual(savedIdent, currentIdent) && CFEqual(savedMod, currentMod)) {
464 cfadd(result, "{%O=#T}", kSecAssessmentAssessmentVerdict);
465 addAuthority(flags, result, "explicit preference");
466 return;
467 }
468 }
469 }
470
471 Xar xar(cfString(path).c_str());
472 if (!xar) {
473 // follow the code signing path
474 evaluateCode(path, type, flags, context, result, true);
475 return;
476 }
477
478 SQLite3::int64 latentID = 0; // first (highest priority) disabled matching ID
479 std::string latentLabel; // ... and associated label, if any
480 if (!xar.isSigned()) {
481 // unsigned xar
482 if (SYSPOLICY_ASSESS_OUTCOME_UNSIGNED_ENABLED())
483 SYSPOLICY_ASSESS_OUTCOME_UNSIGNED(cfString(path).c_str(), type);
484 cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict);
485 addAuthority(flags, result, "no usable signature");
486 return;
487 }
488 if (CFRef<CFArrayRef> certs = xar.copyCertChain()) {
489 CFRef<CFTypeRef> policy = installerPolicy();
490 CFRef<SecTrustRef> trust;
491 MacOSError::check(SecTrustCreateWithCertificates(certs, policy, &trust.aref()));
492 // MacOSError::check(SecTrustSetAnchorCertificates(trust, cfEmptyArray())); // no anchors
493 MacOSError::check(SecTrustSetOptions(trust, kSecTrustOptionAllowExpired | kSecTrustOptionImplicitAnchors));
494
495 SecTrustResultType trustResult;
496 MacOSError::check(SecTrustEvaluate(trust, &trustResult));
497 CFRef<CFArrayRef> chain;
498 CSSM_TP_APPLE_EVIDENCE_INFO *info;
499 MacOSError::check(SecTrustGetResult(trust, &trustResult, &chain.aref(), &info));
500
501 if (flags & kSecAssessmentFlagRequestOrigin)
502 setOrigin(chain, result);
503
504 switch (trustResult) {
505 case kSecTrustResultProceed:
506 case kSecTrustResultUnspecified:
507 break;
508 default:
509 {
510 OSStatus rc;
511 MacOSError::check(SecTrustGetCssmResultCode(trust, &rc));
512 MacOSError::throwMe(rc);
513 }
514 }
515
516 SQLite::Statement query(*this,
517 "SELECT allow, requirement, id, label, flags, disabled FROM scan_authority"
518 " WHERE type = :type"
519 " ORDER BY priority DESC;");
520 query.bind(":type").integer(type);
521 while (query.nextRow()) {
522 bool allow = int(query[0]);
523 const char *reqString = query[1];
524 SQLite3::int64 id = query[2];
525 const char *label = query[3];
526 //sqlite_uint64 ruleFlags = query[4];
527 SQLite3::int64 disabled = query[5];
528
529 CFRef<SecRequirementRef> requirement;
530 MacOSError::check(SecRequirementCreateWithString(CFTempString(reqString), kSecCSDefaultFlags, &requirement.aref()));
531 switch (OSStatus rc = SecRequirementEvaluate(requirement, chain, NULL, kSecCSDefaultFlags)) {
532 case errSecSuccess: // success
533 break;
534 case errSecCSReqFailed: // requirement missed, but otherwise okay
535 continue;
536 default: // broken in some way; all tests will fail like this so bail out
537 MacOSError::throwMe(rc);
538 }
539 if (disabled) {
540 if (latentID == 0) {
541 latentID = id;
542 if (label)
543 latentLabel = label;
544 }
545 continue; // the loop
546 }
547
548 if (SYSPOLICY_ASSESS_OUTCOME_ACCEPT_ENABLED() || SYSPOLICY_ASSESS_OUTCOME_DENY_ENABLED()) {
549 if (allow)
550 SYSPOLICY_ASSESS_OUTCOME_ACCEPT(cfString(path).c_str(), type, label, NULL);
551 else
552 SYSPOLICY_ASSESS_OUTCOME_DENY(cfString(path).c_str(), type, label, NULL);
553 }
554
555 // not adding to the object cache - we could, but it's not likely to be worth it
556 cfadd(result, "{%O=%B}", kSecAssessmentAssessmentVerdict, allow);
557 addAuthority(flags, result, label, id);
558 return;
559 }
560 }
561 if (SYSPOLICY_ASSESS_OUTCOME_DEFAULT_ENABLED())
562 SYSPOLICY_ASSESS_OUTCOME_DEFAULT(cfString(path).c_str(), type, latentLabel.c_str(), NULL);
563
564 // no applicable authority. Deny by default
565 cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict);
566 addAuthority(flags, result, latentLabel.c_str(), latentID);
567 }
568
569
570 //
571 // Create a suitable policy array for verification of installer signatures.
572 //
573 static SecPolicyRef makeCRLPolicy()
574 {
575 CFRef<SecPolicyRef> policy;
576 MacOSError::check(SecPolicyCopy(CSSM_CERT_X_509v3, &CSSMOID_APPLE_TP_REVOCATION_CRL, &policy.aref()));
577 CSSM_APPLE_TP_CRL_OPTIONS options;
578 memset(&options, 0, sizeof(options));
579 options.Version = CSSM_APPLE_TP_CRL_OPTS_VERSION;
580 options.CrlFlags = CSSM_TP_ACTION_FETCH_CRL_FROM_NET | CSSM_TP_ACTION_CRL_SUFFICIENT;
581 CSSM_DATA optData = { sizeof(options), (uint8 *)&options };
582 MacOSError::check(SecPolicySetValue(policy, &optData));
583 return policy.yield();
584 }
585
586 static SecPolicyRef makeOCSPPolicy()
587 {
588 CFRef<SecPolicyRef> policy;
589 MacOSError::check(SecPolicyCopy(CSSM_CERT_X_509v3, &CSSMOID_APPLE_TP_REVOCATION_OCSP, &policy.aref()));
590 CSSM_APPLE_TP_OCSP_OPTIONS options;
591 memset(&options, 0, sizeof(options));
592 options.Version = CSSM_APPLE_TP_OCSP_OPTS_VERSION;
593 options.Flags = CSSM_TP_ACTION_OCSP_SUFFICIENT;
594 CSSM_DATA optData = { sizeof(options), (uint8 *)&options };
595 MacOSError::check(SecPolicySetValue(policy, &optData));
596 return policy.yield();
597 }
598
599 static CFTypeRef installerPolicy()
600 {
601 CFRef<SecPolicyRef> base = SecPolicyCreateBasicX509();
602 CFRef<SecPolicyRef> crl = makeCRLPolicy();
603 CFRef<SecPolicyRef> ocsp = makeOCSPPolicy();
604 return makeCFArray(3, base.get(), crl.get(), ocsp.get());
605 }
606
607
608 //
609 // LaunchServices-layer document open.
610 // We don't cache those at present. If we ever do, we need to authenticate CoreServicesUIAgent as the source of its risk assessment.
611 //
612 void PolicyEngine::evaluateDocOpen(CFURLRef path, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result)
613 {
614 if (context) {
615 if (CFStringRef riskCategory = CFStringRef(CFDictionaryGetValue(context, kLSDownloadRiskCategoryKey))) {
616 FileQuarantine qtn(cfString(path).c_str());
617
618 if (CFEqual(riskCategory, kLSRiskCategorySafe)
619 || CFEqual(riskCategory, kLSRiskCategoryNeutral)
620 || CFEqual(riskCategory, kLSRiskCategoryUnknown)
621 || CFEqual(riskCategory, kLSRiskCategoryMayContainUnsafeExecutable)) {
622 cfadd(result, "{%O=#T}", kSecAssessmentAssessmentVerdict);
623 addAuthority(flags, result, "_XProtect");
624 } else if (qtn.flag(QTN_FLAG_HARD)) {
625 MacOSError::throwMe(errSecCSFileHardQuarantined);
626 } else if (qtn.flag(QTN_FLAG_ASSESSMENT_OK)) {
627 cfadd(result, "{%O=#T}", kSecAssessmentAssessmentVerdict);
628 addAuthority(flags, result, "Prior Assessment");
629 } else if (!overrideAssessment(flags)) { // no need to do more work if we're off
630 try {
631 evaluateCode(path, kAuthorityOpenDoc, flags, context, result, true);
632 } catch (...) {
633 // some documents can't be code signed, so this may be quite benign
634 }
635 }
636 if (CFDictionaryGetValue(result, kSecAssessmentAssessmentVerdict) == NULL) { // no code signature to help us out
637 cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict);
638 addAuthority(flags, result, "_XProtect");
639 }
640 addToAuthority(result, kLSDownloadRiskCategoryKey, riskCategory);
641 return;
642 }
643 }
644 // insufficient information from LS - deny by default
645 cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict);
646 addAuthority(flags, result, "Insufficient Context");
647 }
648
649
650 //
651 // Result-creation helpers
652 //
653 void PolicyEngine::addAuthority(SecAssessmentFlags flags, CFMutableDictionaryRef parent, const char *label, SQLite::int64 row, CFTypeRef cacheInfo, bool weak)
654 {
655 CFRef<CFMutableDictionaryRef> auth = makeCFMutableDictionary();
656 if (label && label[0])
657 cfadd(auth, "{%O=%s}", kSecAssessmentAssessmentSource, label);
658 if (row)
659 CFDictionaryAddValue(auth, kSecAssessmentAssessmentAuthorityRow, CFTempNumber(row));
660 if (overrideAssessment(flags))
661 CFDictionaryAddValue(auth, kSecAssessmentAssessmentAuthorityOverride, kDisabledOverride);
662 if (cacheInfo)
663 CFDictionaryAddValue(auth, kSecAssessmentAssessmentFromCache, cacheInfo);
664 if (weak) {
665 CFDictionaryAddValue(auth, kSecAssessmentAssessmentWeakSignature, kCFBooleanTrue);
666 CFDictionaryReplaceValue(parent, kSecAssessmentAssessmentAuthority, auth);
667 } else {
668 CFDictionaryAddValue(parent, kSecAssessmentAssessmentAuthority, auth);
669 }
670 }
671
672 void PolicyEngine::addToAuthority(CFMutableDictionaryRef parent, CFStringRef key, CFTypeRef value)
673 {
674 CFMutableDictionaryRef authority = CFMutableDictionaryRef(CFDictionaryGetValue(parent, kSecAssessmentAssessmentAuthority));
675 assert(authority);
676 CFDictionaryAddValue(authority, key, value);
677 }
678
679
680 //
681 // Add a rule to the policy database
682 //
683 CFDictionaryRef PolicyEngine::add(CFTypeRef inTarget, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context)
684 {
685 // default type to execution
686 if (type == kAuthorityInvalid)
687 type = kAuthorityExecute;
688
689 authorizeUpdate(flags, context);
690 CFDictionary ctx(context, errSecCSInvalidAttributeValues);
691 CFCopyRef<CFTypeRef> target = inTarget;
692 CFRef<CFDataRef> bookmark = NULL;
693 std::string filter_unsigned;
694
695 switch (type) {
696 case kAuthorityExecute:
697 normalizeTarget(target, type, ctx, &filter_unsigned);
698 // bookmarks are untrusted and just a hint to callers
699 bookmark = ctx.get<CFDataRef>(kSecAssessmentRuleKeyBookmark);
700 break;
701 case kAuthorityInstall:
702 if (inTarget && CFGetTypeID(inTarget) == CFURLGetTypeID()) {
703 // no good way to turn an installer file into a requirement. Pretend to succeeed so caller proceeds
704 CFRef<CFArrayRef> properties = makeCFArray(2, kCFURLFileResourceIdentifierKey, kCFURLContentModificationDateKey);
705 CFRef<CFErrorRef> error;
706 CFURLBookmarkCreationOptions options = kCFURLBookmarkCreationDoNotIncludeSandboxExtensionsMask | kCFURLBookmarkCreationMinimalBookmarkMask;
707 if (CFRef<CFDataRef> bookmark = CFURLCreateBookmarkData(NULL, CFURLRef(inTarget), options, properties, NULL, &error.aref())) {
708 UnixPlusPlus::AutoFileDesc fd(lastApprovedFile, O_WRONLY | O_CREAT | O_TRUNC);
709 fd.write(CFDataGetBytePtr(bookmark), CFDataGetLength(bookmark));
710 return NULL;
711 }
712 }
713 break;
714 case kAuthorityOpenDoc:
715 // handle document-open differently: use quarantine flags for whitelisting
716 if (!target || CFGetTypeID(target) != CFURLGetTypeID()) // can only "add" file paths
717 MacOSError::throwMe(errSecCSInvalidObjectRef);
718 try {
719 std::string spath = cfString(target.as<CFURLRef>());
720 FileQuarantine qtn(spath.c_str());
721 qtn.setFlag(QTN_FLAG_ASSESSMENT_OK);
722 qtn.applyTo(spath.c_str());
723 } catch (const CommonError &error) {
724 // could not set quarantine flag - report qualified success
725 return cfmake<CFDictionaryRef>("{%O=%O,'assessment:error'=%d}",
726 kSecAssessmentAssessmentAuthorityOverride, CFSTR("error setting quarantine"), error.osStatus());
727 } catch (...) {
728 return cfmake<CFDictionaryRef>("{%O=%O}", kSecAssessmentAssessmentAuthorityOverride, CFSTR("unable to set quarantine"));
729 }
730 return NULL;
731 }
732
733 // if we now have anything else, we're busted
734 if (!target || CFGetTypeID(target) != SecRequirementGetTypeID())
735 MacOSError::throwMe(errSecCSInvalidObjectRef);
736
737 double priority = 0;
738 string label;
739 bool allow = true;
740 double expires = never;
741 string remarks;
742 SQLite::uint64 dbFlags = kAuthorityFlagWhitelistV2 | kAuthorityFlagWhitelistSHA256;
743
744 if (CFNumberRef pri = ctx.get<CFNumberRef>(kSecAssessmentUpdateKeyPriority))
745 CFNumberGetValue(pri, kCFNumberDoubleType, &priority);
746 if (CFStringRef lab = ctx.get<CFStringRef>(kSecAssessmentUpdateKeyLabel))
747 label = cfString(lab);
748 if (CFDateRef time = ctx.get<CFDateRef>(kSecAssessmentUpdateKeyExpires))
749 // we're using Julian dates here; convert from CFDate
750 expires = dateToJulian(time);
751 if (CFBooleanRef allowing = ctx.get<CFBooleanRef>(kSecAssessmentUpdateKeyAllow))
752 allow = allowing == kCFBooleanTrue;
753 if (CFStringRef rem = ctx.get<CFStringRef>(kSecAssessmentUpdateKeyRemarks))
754 remarks = cfString(rem);
755
756 CFRef<CFStringRef> requirementText;
757 MacOSError::check(SecRequirementCopyString(target.as<SecRequirementRef>(), kSecCSDefaultFlags, &requirementText.aref()));
758 SQLite::Transaction xact(*this, SQLite3::Transaction::deferred, "add_rule");
759 SQLite::Statement insert(*this,
760 "INSERT INTO authority (type, allow, requirement, priority, label, expires, filter_unsigned, remarks, flags)"
761 " VALUES (:type, :allow, :requirement, :priority, :label, :expires, :filter_unsigned, :remarks, :flags);");
762 insert.bind(":type").integer(type);
763 insert.bind(":allow").integer(allow);
764 insert.bind(":requirement") = requirementText.get();
765 insert.bind(":priority") = priority;
766 if (!label.empty())
767 insert.bind(":label") = label;
768 insert.bind(":expires") = expires;
769 insert.bind(":filter_unsigned") = filter_unsigned.empty() ? NULL : filter_unsigned.c_str();
770 if (!remarks.empty())
771 insert.bind(":remarks") = remarks;
772 insert.bind(":flags").integer(dbFlags);
773 insert.execute();
774 SQLite::int64 newRow = this->lastInsert();
775 if (bookmark) {
776 SQLite::Statement bi(*this, "INSERT INTO bookmarkhints (bookmark, authority) VALUES (:bookmark, :authority)");
777 bi.bind(":bookmark") = CFDataRef(bookmark);
778 bi.bind(":authority").integer(newRow);
779 bi.execute();
780 }
781 this->purgeObjects(priority);
782 xact.commit();
783 notify_post(kNotifySecAssessmentUpdate);
784 return cfmake<CFDictionaryRef>("{%O=%d}", kSecAssessmentUpdateKeyRow, newRow);
785 }
786
787
788 CFDictionaryRef PolicyEngine::remove(CFTypeRef target, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context)
789 {
790 if (type == kAuthorityOpenDoc) {
791 // handle document-open differently: use quarantine flags for whitelisting
792 authorizeUpdate(flags, context);
793 if (!target || CFGetTypeID(target) != CFURLGetTypeID())
794 MacOSError::throwMe(errSecCSInvalidObjectRef);
795 std::string spath = cfString(CFURLRef(target)).c_str();
796 FileQuarantine qtn(spath.c_str());
797 qtn.clearFlag(QTN_FLAG_ASSESSMENT_OK);
798 qtn.applyTo(spath.c_str());
799 return NULL;
800 }
801 return manipulateRules("DELETE FROM authority", target, type, flags, context, true);
802 }
803
804 CFDictionaryRef PolicyEngine::enable(CFTypeRef target, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context, bool authorize)
805 {
806 return manipulateRules("UPDATE authority SET disabled = 0", target, type, flags, context, authorize);
807 }
808
809 CFDictionaryRef PolicyEngine::disable(CFTypeRef target, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context, bool authorize)
810 {
811 return manipulateRules("UPDATE authority SET disabled = 1", target, type, flags, context, authorize);
812 }
813
814 CFDictionaryRef PolicyEngine::find(CFTypeRef target, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context)
815 {
816 SQLite::Statement query(*this);
817 selectRules(query, "SELECT scan_authority.id, scan_authority.type, scan_authority.requirement, scan_authority.allow, scan_authority.label, scan_authority.priority, scan_authority.remarks, scan_authority.expires, scan_authority.disabled, bookmarkhints.bookmark FROM scan_authority LEFT OUTER JOIN bookmarkhints ON scan_authority.id = bookmarkhints.authority",
818 "scan_authority", target, type, flags, context,
819 " ORDER BY priority DESC");
820 CFRef<CFMutableArrayRef> found = makeCFMutableArray(0);
821 while (query.nextRow()) {
822 SQLite::int64 id = query[0];
823 int type = int(query[1]);
824 const char *requirement = query[2];
825 int allow = int(query[3]);
826 const char *label = query[4];
827 double priority = query[5];
828 const char *remarks = query[6];
829 double expires = query[7];
830 int disabled = int(query[8]);
831 CFRef<CFDataRef> bookmark = query[9].data();
832 CFRef<CFMutableDictionaryRef> rule = makeCFMutableDictionary(5,
833 kSecAssessmentRuleKeyID, CFTempNumber(id).get(),
834 kSecAssessmentRuleKeyType, CFRef<CFStringRef>(typeNameFor(type)).get(),
835 kSecAssessmentRuleKeyRequirement, CFTempString(requirement).get(),
836 kSecAssessmentRuleKeyAllow, allow ? kCFBooleanTrue : kCFBooleanFalse,
837 kSecAssessmentRuleKeyPriority, CFTempNumber(priority).get()
838 );
839 if (label)
840 CFDictionaryAddValue(rule, kSecAssessmentRuleKeyLabel, CFTempString(label));
841 if (remarks)
842 CFDictionaryAddValue(rule, kSecAssessmentRuleKeyRemarks, CFTempString(remarks));
843 if (expires != never)
844 CFDictionaryAddValue(rule, kSecAssessmentRuleKeyExpires, CFRef<CFDateRef>(julianToDate(expires)));
845 if (disabled)
846 CFDictionaryAddValue(rule, kSecAssessmentRuleKeyDisabled, CFTempNumber(disabled));
847 if (bookmark)
848 CFDictionaryAddValue(rule, kSecAssessmentRuleKeyBookmark, bookmark);
849 CFArrayAppendValue(found, rule);
850 }
851 if (CFArrayGetCount(found) == 0)
852 MacOSError::throwMe(errSecCSNoMatches);
853 return cfmake<CFDictionaryRef>("{%O=%O}", kSecAssessmentUpdateKeyFound, found.get());
854 }
855
856
857 CFDictionaryRef PolicyEngine::update(CFTypeRef target, SecAssessmentFlags flags, CFDictionaryRef context)
858 {
859 // update GKE
860 installExplicitSet(gkeAuthFile, gkeSigsFile);
861
862 AuthorityType type = typeFor(context, kAuthorityInvalid);
863 CFStringRef edit = CFStringRef(CFDictionaryGetValue(context, kSecAssessmentContextKeyUpdate));
864 CFDictionaryRef result;
865 if (CFEqual(edit, kSecAssessmentUpdateOperationAdd))
866 result = this->add(target, type, flags, context);
867 else if (CFEqual(edit, kSecAssessmentUpdateOperationRemove))
868 result = this->remove(target, type, flags, context);
869 else if (CFEqual(edit, kSecAssessmentUpdateOperationEnable))
870 result = this->enable(target, type, flags, context, true);
871 else if (CFEqual(edit, kSecAssessmentUpdateOperationDisable))
872 result = this->disable(target, type, flags, context, true);
873 else if (CFEqual(edit, kSecAssessmentUpdateOperationFind))
874 result = this->find(target, type, flags, context);
875 else
876 MacOSError::throwMe(errSecCSInvalidAttributeValues);
877 if (result == NULL)
878 result = makeCFDictionary(0); // success, no details
879 return result;
880 }
881
882
883 //
884 // Construct and prepare an SQL query on the authority table, operating on some set of existing authority records.
885 // In essence, this appends a suitable WHERE clause to the stanza passed and prepares it on the statement given.
886 //
887 void PolicyEngine::selectRules(SQLite::Statement &action, std::string phrase, std::string table,
888 CFTypeRef inTarget, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context, std::string suffix /* = "" */)
889 {
890 CFDictionary ctx(context, errSecCSInvalidAttributeValues);
891 CFCopyRef<CFTypeRef> target = inTarget;
892 std::string filter_unsigned; // ignored; used just to trigger ad-hoc signing
893 normalizeTarget(target, type, ctx, &filter_unsigned);
894
895 string label;
896 if (CFStringRef lab = ctx.get<CFStringRef>(kSecAssessmentUpdateKeyLabel))
897 label = cfString(CFStringRef(lab));
898
899 if (!target) {
900 if (label.empty()) {
901 if (type == kAuthorityInvalid) {
902 action.query(phrase + suffix);
903 } else {
904 action.query(phrase + " WHERE " + table + ".type = :type" + suffix);
905 action.bind(":type").integer(type);
906 }
907 } else { // have label
908 if (type == kAuthorityInvalid) {
909 action.query(phrase + " WHERE " + table + ".label = :label" + suffix);
910 } else {
911 action.query(phrase + " WHERE " + table + ".type = :type AND " + table + ".label = :label" + suffix);
912 action.bind(":type").integer(type);
913 }
914 action.bind(":label") = label;
915 }
916 } else if (CFGetTypeID(target) == CFNumberGetTypeID()) {
917 action.query(phrase + " WHERE " + table + ".id = :id" + suffix);
918 action.bind(":id").integer(cfNumber<uint64_t>(target.as<CFNumberRef>()));
919 } else if (CFGetTypeID(target) == SecRequirementGetTypeID()) {
920 if (type == kAuthorityInvalid)
921 type = kAuthorityExecute;
922 CFRef<CFStringRef> requirementText;
923 MacOSError::check(SecRequirementCopyString(target.as<SecRequirementRef>(), kSecCSDefaultFlags, &requirementText.aref()));
924 action.query(phrase + " WHERE " + table + ".type = :type AND " + table + ".requirement = :requirement" + suffix);
925 action.bind(":type").integer(type);
926 action.bind(":requirement") = requirementText.get();
927 } else
928 MacOSError::throwMe(errSecCSInvalidObjectRef);
929 }
930
931
932 //
933 // Execute an atomic change to existing records in the authority table.
934 //
935 CFDictionaryRef PolicyEngine::manipulateRules(const std::string &stanza,
936 CFTypeRef inTarget, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context, bool authorize)
937 {
938 SQLite::Transaction xact(*this, SQLite3::Transaction::deferred, "rule_change");
939 SQLite::Statement action(*this);
940 if (authorize)
941 authorizeUpdate(flags, context);
942 selectRules(action, stanza, "authority", inTarget, type, flags, context);
943 action.execute();
944 unsigned int changes = this->changes(); // latch change count
945 // We MUST purge objects with priority <= MAX(priority of any changed rules);
946 // but for now we just get lazy and purge them ALL.
947 if (changes) {
948 this->purgeObjects(1.0E100);
949 xact.commit();
950 notify_post(kNotifySecAssessmentUpdate);
951 return cfmake<CFDictionaryRef>("{%O=%d}", kSecAssessmentUpdateKeyCount, changes);
952 }
953 // no change; return an error
954 MacOSError::throwMe(errSecCSNoMatches);
955 }
956
957
958 //
959 // Fill in extra information about the originator of cryptographic credentials found - if any
960 //
961 void PolicyEngine::setOrigin(CFArrayRef chain, CFMutableDictionaryRef result)
962 {
963 if (chain)
964 if (CFArrayGetCount(chain) > 0)
965 if (SecCertificateRef leaf = SecCertificateRef(CFArrayGetValueAtIndex(chain, 0)))
966 if (CFStringRef summary = SecCertificateCopyLongDescription(NULL, leaf, NULL)) {
967 CFDictionarySetValue(result, kSecAssessmentAssessmentOriginator, summary);
968 CFRelease(summary);
969 }
970 }
971
972
973 //
974 // Take an assessment outcome and record it in the object cache
975 //
976 void PolicyEngine::recordOutcome(SecStaticCodeRef code, bool allow, AuthorityType type, double expires, SQLite::int64 authority)
977 {
978 CFRef<CFDictionaryRef> info;
979 MacOSError::check(SecCodeCopySigningInformation(code, kSecCSDefaultFlags, &info.aref()));
980 CFDataRef cdHash = CFDataRef(CFDictionaryGetValue(info, kSecCodeInfoUnique));
981 assert(cdHash); // was signed
982 CFRef<CFURLRef> path;
983 MacOSError::check(SecCodeCopyPath(code, kSecCSDefaultFlags, &path.aref()));
984 assert(expires);
985 SQLite::Transaction xact(*this, SQLite3::Transaction::deferred, "caching");
986 SQLite::Statement insert(*this,
987 "INSERT OR REPLACE INTO object (type, allow, hash, expires, path, authority)"
988 " VALUES (:type, :allow, :hash, :expires, :path,"
989 " CASE :authority WHEN 0 THEN (SELECT id FROM authority WHERE label = 'No Matching Rule') ELSE :authority END"
990 " );");
991 insert.bind(":type").integer(type);
992 insert.bind(":allow").integer(allow);
993 insert.bind(":hash") = cdHash;
994 insert.bind(":expires") = expires;
995 insert.bind(":path") = cfString(path);
996 insert.bind(":authority").integer(authority);
997 insert.execute();
998 xact.commit();
999 }
1000
1001
1002 //
1003 // Record a UI failure record after proper validation of the caller
1004 //
1005 void PolicyEngine::recordFailure(CFDictionaryRef info)
1006 {
1007 CFRef<CFDataRef> infoData = makeCFData(info);
1008 UnixPlusPlus::AutoFileDesc fd(lastRejectFile, O_WRONLY | O_CREAT | O_TRUNC);
1009 fd.write(CFDataGetBytePtr(infoData), CFDataGetLength(infoData));
1010 notify_post(kNotifySecAssessmentRecordingChange);
1011 }
1012
1013
1014 //
1015 // Perform update authorization processing.
1016 // Throws an exception if authorization is denied.
1017 //
1018 static void authorizeUpdate(SecAssessmentFlags flags, CFDictionaryRef context)
1019 {
1020 AuthorizationRef authorization = NULL;
1021
1022 if (context)
1023 if (CFTypeRef authkey = CFDictionaryGetValue(context, kSecAssessmentUpdateKeyAuthorization))
1024 if (CFGetTypeID(authkey) == CFDataGetTypeID()) {
1025 CFDataRef authdata = CFDataRef(authkey);
1026 if (CFDataGetLength(authdata) != sizeof(AuthorizationExternalForm))
1027 MacOSError::throwMe(errSecCSInvalidObjectRef);
1028 MacOSError::check(AuthorizationCreateFromExternalForm((AuthorizationExternalForm *)CFDataGetBytePtr(authdata), &authorization));
1029 }
1030 if (authorization == NULL)
1031 MacOSError::throwMe(errSecCSDBDenied);
1032
1033 AuthorizationItem right[] = {
1034 { "com.apple.security.assessment.update", 0, NULL, 0 }
1035 };
1036 AuthorizationRights rights = { sizeof(right) / sizeof(right[0]), right };
1037 MacOSError::check(AuthorizationCopyRights(authorization, &rights, NULL,
1038 kAuthorizationFlagExtendRights | kAuthorizationFlagInteractionAllowed, NULL));
1039
1040 MacOSError::check(AuthorizationFree(authorization, kAuthorizationFlagDefaults));
1041 }
1042
1043
1044 //
1045 // Perform common argument normalizations for update operations
1046 //
1047 void PolicyEngine::normalizeTarget(CFRef<CFTypeRef> &target, AuthorityType type, CFDictionary &context, std::string *signUnsigned)
1048 {
1049 // turn CFURLs into (designated) SecRequirements
1050 if (target && CFGetTypeID(target) == CFURLGetTypeID()) {
1051 CFRef<SecStaticCodeRef> code;
1052 CFURLRef path = target.as<CFURLRef>();
1053 MacOSError::check(SecStaticCodeCreateWithPath(path, kSecCSDefaultFlags, &code.aref()));
1054 switch (OSStatus rc = SecCodeCopyDesignatedRequirement(code, kSecCSDefaultFlags, (SecRequirementRef *)&target.aref())) {
1055 case errSecSuccess: {
1056 // use the *default* DR to avoid unreasonably wide DRs opening up Gatekeeper to attack
1057 CFRef<CFDictionaryRef> info;
1058 MacOSError::check(SecCodeCopySigningInformation(code, kSecCSRequirementInformation, &info.aref()));
1059 target = CFDictionaryGetValue(info, kSecCodeInfoImplicitDesignatedRequirement);
1060 }
1061 break;
1062 case errSecCSUnsigned:
1063 if (signUnsigned && temporarySigning(code, type, path, kAuthorityFlagWhitelistV2 | kAuthorityFlagWhitelistSHA256)) { // ad-hoc sign the code temporarily
1064 MacOSError::check(SecCodeCopyDesignatedRequirement(code, kSecCSDefaultFlags, (SecRequirementRef *)&target.aref()));
1065 CFRef<CFDictionaryRef> info;
1066 MacOSError::check(SecCodeCopySigningInformation(code, kSecCSInternalInformation, &info.aref()));
1067 if (CFDataRef cdData = CFDataRef(CFDictionaryGetValue(info, kSecCodeInfoCodeDirectory)))
1068 *signUnsigned = ((const CodeDirectory *)CFDataGetBytePtr(cdData))->screeningCode();
1069 break;
1070 }
1071 MacOSError::check(rc);
1072 case errSecCSSignatureFailed:
1073 // recover certain cases of broken signatures (well, try)
1074 if (codeInvalidityExceptions(code, NULL)) {
1075 // Ad-hoc sign the code in place (requiring a writable subject). This requires root privileges.
1076 CFRef<SecCodeSignerRef> signer;
1077 CFTemp<CFDictionaryRef> arguments("{%O=#N}", kSecCodeSignerIdentity);
1078 MacOSError::check(SecCodeSignerCreate(arguments, kSecCSSignOpaque, &signer.aref()));
1079 MacOSError::check(SecCodeSignerAddSignature(signer, code, kSecCSDefaultFlags));
1080 MacOSError::check(SecCodeCopyDesignatedRequirement(code, kSecCSDefaultFlags, (SecRequirementRef *)&target.aref()));
1081 break;
1082 }
1083 MacOSError::check(rc);
1084 default:
1085 MacOSError::check(rc);
1086 }
1087 if (context.get(kSecAssessmentUpdateKeyRemarks) == NULL) {
1088 // no explicit remarks; add one with the path
1089 CFRef<CFURLRef> path;
1090 MacOSError::check(SecCodeCopyPath(code, kSecCSDefaultFlags, &path.aref()));
1091 CFMutableDictionaryRef dict = makeCFMutableDictionary(context.get());
1092 CFDictionaryAddValue(dict, kSecAssessmentUpdateKeyRemarks, CFTempString(cfString(path)));
1093 context.take(dict);
1094 }
1095 CFStringRef edit = CFStringRef(context.get(kSecAssessmentContextKeyUpdate));
1096 if (type == kAuthorityExecute && CFEqual(edit, kSecAssessmentUpdateOperationAdd)) {
1097 // implicitly whitelist the code
1098 mOpaqueWhitelist.add(code);
1099 }
1100 }
1101 }
1102
1103
1104 //
1105 // Process special overrides for invalidly signed code.
1106 // This is the (hopefully minimal) concessions we make to keep hurting our customers
1107 // for our own prior mistakes...
1108 //
1109 static bool codeInvalidityExceptions(SecStaticCodeRef code, CFMutableDictionaryRef result)
1110 {
1111 if (OSAIsRecognizedExecutableURL) {
1112 CFRef<CFDictionaryRef> info;
1113 MacOSError::check(SecCodeCopySigningInformation(code, kSecCSDefaultFlags, &info.aref()));
1114 if (CFURLRef executable = CFURLRef(CFDictionaryGetValue(info, kSecCodeInfoMainExecutable))) {
1115 SInt32 error;
1116 if (OSAIsRecognizedExecutableURL(executable, &error)) {
1117 if (result)
1118 CFDictionaryAddValue(result,
1119 kSecAssessmentAssessmentAuthorityOverride, CFSTR("ignoring known invalid applet signature"));
1120 return true;
1121 }
1122 }
1123 }
1124 return false;
1125 }
1126
1127
1128 } // end namespace CodeSigning
1129 } // end namespace Security