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