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