2 * Copyright (c) 2011-2016 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
23 #include "policyengine.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>
39 #include "codedirectory.h"
40 #include "csutilities.h"
41 #include "notarization.h"
42 #include "StaticCode.h"
44 #include <CoreServices/CoreServicesPriv.h>
45 #include "SecCodePriv.h"
46 #undef check // Macro! Yech.
49 #include <OpenScriptingUtilPriv.h>
54 namespace CodeSigning
{
56 static const double NEGATIVE_HOLD
= 60.0/86400; // 60 seconds to cache negative outcomes
58 static const char RECORDER_DIR
[] = "/tmp/gke-"; // recorder mode destination for detached signatures
60 recorder_code_untrusted
= 0, // signed but untrusted
61 recorder_code_adhoc
= 1, // unsigned; signature recorded
62 recorder_code_unable
= 2, // unsigned; unable to record signature
66 static void authorizeUpdate(SecAssessmentFlags flags
, CFDictionaryRef context
);
67 static bool codeInvalidityExceptions(SecStaticCodeRef code
, CFMutableDictionaryRef result
);
68 static CFTypeRef
installerPolicy() CF_RETURNS_RETAINED
;
74 PolicyEngine::PolicyEngine()
75 : PolicyDatabase(NULL
, SQLITE_OPEN_READWRITE
| SQLITE_OPEN_CREATE
)
78 mOpaqueWhitelist
= new OpaqueWhitelist();
80 mOpaqueWhitelist
= NULL
;
81 secerror("Failed opening the gkopaque database.");
85 PolicyEngine::~PolicyEngine()
87 delete mOpaqueWhitelist
;
92 // Top-level evaluation driver
94 void PolicyEngine::evaluate(CFURLRef path
, AuthorityType type
, SecAssessmentFlags flags
, CFDictionaryRef context
, CFMutableDictionaryRef result
)
97 installExplicitSet(gkeAuthFile
, gkeSigsFile
);
99 // find the global evaluation manager
100 EvaluationManager
*evaluationManager
= EvaluationManager::globalManager();
102 // perform the evaluation
103 EvaluationTask
*evaluationTask
= evaluationManager
->evaluationTask(this, path
, type
, flags
, context
, result
);
104 evaluationManager
->finalizeTask(evaluationTask
, flags
, result
);
106 // if rejected, reset the automatic rearm timer
107 if (CFDictionaryGetValue(result
, kSecAssessmentAssessmentVerdict
) == kCFBooleanFalse
)
108 resetRearmTimer("reject");
113 // Create GKE whitelist filter screens.
114 // These are strings that are used to determine quickly whether unsigned code may
115 // have a GKE-style whitelist entry in the authority database. The idea is to make
116 // up a decent hash quickly.
118 // Note: We continue to use SHA1 here for compatibility of existing GKE entries.
119 // These are a prescreen, backed up by code signature checks later on. Use of SHA1 here is not a security problem.
121 static std::string
createWhitelistScreen(char type
, const Byte
*digest
, size_t length
)
123 char buffer
[2*length
+ 2];
125 for (size_t n
= 0; n
< length
; n
++)
126 sprintf(buffer
+ 1 + 2*n
, "%02.2x", digest
[n
]);
130 static std::string
createWhitelistScreen(SecStaticCodeRef code
)
132 DiskRep
*rep
= SecStaticCode::requiredStatic(code
)->diskRep();
134 if (CFRef
<CFDataRef
> info
= rep
->component(cdInfoSlot
)) {
135 // has an Info.plist - hash it
137 hash
.update(CFDataGetBytePtr(info
), CFDataGetLength(info
));
140 return createWhitelistScreen('I', digest
, sizeof(digest
));
141 } else if (CFRef
<CFDataRef
> repSpecific
= rep
->component(cdRepSpecificSlot
)) {
142 // has a rep-specific slot - hash that (this catches disk images cheaply)
143 // got invented after SHA-1 deprecation, so we'll use SHA256, which is the new default
144 CCHashInstance
hash(kCCDigestSHA256
);
145 hash
.update(CFDataGetBytePtr(repSpecific
), CFDataGetLength(repSpecific
));
148 return createWhitelistScreen('R', digest
, sizeof(digest
));
149 } else if (rep
->mainExecutableImage()) {
150 // stand-alone Mach-O executables are always candidates
153 // if everything else fails, hash the (single) file
155 hashFileData(rep
->mainExecutablePath().c_str(), &hash
);
158 return createWhitelistScreen('M', digest
, sizeof(digest
));
163 void PolicyEngine::evaluateCodeItem(SecStaticCodeRef code
, CFURLRef path
, AuthorityType type
, SecAssessmentFlags flags
, bool nested
, CFMutableDictionaryRef result
)
166 SQLite::Statement
query(*this,
167 "SELECT allow, requirement, id, label, expires, flags, disabled, filter_unsigned, remarks FROM scan_authority"
168 " WHERE type = :type"
169 " ORDER BY priority DESC;");
170 query
.bind(":type").integer(type
);
172 SQLite3::int64 latentID
= 0; // first (highest priority) disabled matching ID
173 std::string latentLabel
; // ... and associated label, if any
175 secdebug("gk", "evaluateCodeItem type=%d flags=0x%x nested=%d path=%s", type
, int(flags
), nested
, cfString(path
).c_str());
176 while (query
.nextRow()) {
177 bool allow
= int(query
[0]);
178 const char *reqString
= query
[1];
179 SQLite3::int64 id
= query
[2];
180 const char *label
= query
[3];
181 double expires
= query
[4];
182 sqlite3_int64 ruleFlags
= query
[5];
183 SQLite3::int64 disabled
= query
[6];
184 // const char *filter = query[7];
185 // const char *remarks = query[8];
187 secdebug("gk", "considering rule %d(%s) requirement %s", int(id
), label
? label
: "UNLABELED", reqString
);
188 CFRef
<SecRequirementRef
> requirement
;
189 MacOSError::check(SecRequirementCreateWithString(CFTempString(reqString
), kSecCSDefaultFlags
, &requirement
.aref()));
190 switch (OSStatus rc
= SecStaticCodeCheckValidity(code
, kSecCSBasicValidateOnly
| kSecCSCheckGatekeeperArchitectures
, requirement
)) {
192 break; // rule match; process below
193 case errSecCSReqFailed
:
194 continue; // rule does not apply
196 return; // nested code has failed to pass
198 MacOSError::throwMe(rc
); // general error; pass to caller
201 // If this rule is disabled, do not continue any further and just continue iterating
202 // until we find one that is enabled.
204 // ...but always record the first matching rule for informational purposes.
207 latentLabel
= label
? label
: "";
212 // current rule is first rule (in priority order) that matched. Apply it
213 secnotice("gk", "rule %d applies - allow=%d", int(id
), allow
);
214 if (nested
&& allow
) // success, nothing to record
217 CFRef
<CFDictionaryRef
> info
; // as needed
218 if (flags
& kSecAssessmentFlagRequestOrigin
) {
220 MacOSError::check(SecCodeCopySigningInformation(code
, kSecCSSigningInformation
, &info
.aref()));
221 if (CFArrayRef chain
= CFArrayRef(CFDictionaryGetValue(info
, kSecCodeInfoCertificates
)))
222 setOrigin(chain
, result
);
224 if (!(ruleFlags
& kAuthorityFlagInhibitCache
) && !(flags
& kSecAssessmentFlagNoCache
)) { // cache inhibit
226 MacOSError::check(SecCodeCopySigningInformation(code
, kSecCSSigningInformation
, &info
.aref()));
227 if (SecTrustRef trust
= SecTrustRef(CFDictionaryGetValue(info
, kSecCodeInfoTrust
))) {
228 CFRef
<CFDictionaryRef
> xinfo
;
229 MacOSError::check(SecTrustCopyExtendedResult(trust
, &xinfo
.aref()));
230 if (CFDateRef limit
= CFDateRef(CFDictionaryGetValue(xinfo
, kSecTrustExpirationDate
))) {
231 this->recordOutcome(code
, allow
, type
, min(expires
, dateToJulian(limit
)), id
);
236 if (SYSPOLICY_ASSESS_OUTCOME_ACCEPT_ENABLED()) {
238 MacOSError::check(SecCodeCopySigningInformation(code
, kSecCSSigningInformation
, &info
.aref()));
239 CFDataRef cdhash
= CFDataRef(CFDictionaryGetValue(info
, kSecCodeInfoUnique
));
240 SYSPOLICY_ASSESS_OUTCOME_ACCEPT(cfString(path
).c_str(), type
, label
, cdhash
? CFDataGetBytePtr(cdhash
) : NULL
);
243 if (SYSPOLICY_ASSESS_OUTCOME_DENY_ENABLED() || SYSPOLICY_RECORDER_MODE_ENABLED()) {
245 MacOSError::check(SecCodeCopySigningInformation(code
, kSecCSSigningInformation
, &info
.aref()));
246 CFDataRef cdhash
= CFDataRef(CFDictionaryGetValue(info
, kSecCodeInfoUnique
));
247 std::string cpath
= cfString(path
);
248 const void *hashp
= cdhash
? CFDataGetBytePtr(cdhash
) : NULL
;
249 SYSPOLICY_ASSESS_OUTCOME_DENY(cpath
.c_str(), type
, label
, hashp
);
250 SYSPOLICY_RECORDER_MODE(cpath
.c_str(), type
, label
, hashp
, recorder_code_untrusted
);
253 cfadd(result
, "{%O=%B}", kSecAssessmentAssessmentVerdict
, allow
);
254 addAuthority(flags
, result
, label
, id
, NULL
, false, ruleFlags
);
258 // no applicable authority (but signed, perhaps temporarily). Deny by default
259 secnotice("gk", "rejecting due to lack of matching active rule");
260 CFRef
<CFDictionaryRef
> info
;
261 MacOSError::check(SecCodeCopySigningInformation(code
, kSecCSSigningInformation
, &info
.aref()));
262 if (flags
& kSecAssessmentFlagRequestOrigin
) {
263 if (CFArrayRef chain
= CFArrayRef(CFDictionaryGetValue(info
, kSecCodeInfoCertificates
)))
264 setOrigin(chain
, result
);
266 if (SYSPOLICY_ASSESS_OUTCOME_DEFAULT_ENABLED() || SYSPOLICY_RECORDER_MODE_ENABLED()) {
267 CFDataRef cdhash
= CFDataRef(CFDictionaryGetValue(info
, kSecCodeInfoUnique
));
268 const void *hashp
= cdhash
? CFDataGetBytePtr(cdhash
) : NULL
;
269 std::string cpath
= cfString(path
);
270 SYSPOLICY_ASSESS_OUTCOME_DEFAULT(cpath
.c_str(), type
, latentLabel
.c_str(), hashp
);
271 SYSPOLICY_RECORDER_MODE(cpath
.c_str(), type
, latentLabel
.c_str(), hashp
, 0);
273 if (!(flags
& kSecAssessmentFlagNoCache
))
274 this->recordOutcome(code
, false, type
, this->julianNow() + NEGATIVE_HOLD
, latentID
);
275 cfadd(result
, "{%O=%B}", kSecAssessmentAssessmentVerdict
, false);
276 addAuthority(flags
, result
, latentLabel
.c_str(), latentID
);
279 CFDictionaryRef
PolicyEngine::opaqueWhitelistValidationConditionsFor(SecStaticCodeRef code
)
281 return (mOpaqueWhitelist
!= NULL
) ? mOpaqueWhitelist
->validationConditionsFor(code
) : NULL
;
284 bool PolicyEngine::opaqueWhiteListContains(SecStaticCodeRef code
, SecAssessmentFeedback feedback
, OSStatus reason
)
286 return (mOpaqueWhitelist
!= NULL
) ? mOpaqueWhitelist
->contains(code
, feedback
, reason
) : false;
289 void PolicyEngine::opaqueWhitelistAdd(SecStaticCodeRef code
)
291 if (mOpaqueWhitelist
) {
292 mOpaqueWhitelist
->add(code
);
296 void PolicyEngine::adjustValidation(SecStaticCodeRef code
)
298 CFRef
<CFDictionaryRef
> conditions
= opaqueWhitelistValidationConditionsFor(code
);
299 SecStaticCodeSetValidationConditions(code
, conditions
);
303 bool PolicyEngine::temporarySigning(SecStaticCodeRef code
, AuthorityType type
, CFURLRef path
, SecAssessmentFlags matchFlags
)
305 secnotice("gk", "temporarySigning type=%d matchFlags=0x%x path=%s", type
, int(matchFlags
), cfString(path
).c_str());
307 // see if we have a screened record to take matchFlags from
308 std::string screen
= createWhitelistScreen(code
);
309 SQLite::Statement
query(*this,
310 "SELECT flags FROM authority "
312 " AND NOT flags & :flag"
313 " AND CASE WHEN filter_unsigned IS NULL THEN remarks = :remarks ELSE filter_unsigned = :screen END");
314 query
.bind(":type").integer(type
);
315 query
.bind(":flag").integer(kAuthorityFlagDefault
);
316 query
.bind(":screen") = screen
;
317 query
.bind(":remarks") = cfString(path
);
318 secdebug("gk", "match screen=%s", screen
.c_str());
319 if (query
.nextRow()) // got a matching rule
320 matchFlags
= SQLite3::int64(query
[0]);
321 else if (matchFlags
== 0) // lazy and no match
323 secdebug("gk", "matchFlags found=0x%x", int(matchFlags
));
326 // ad-hoc sign the code and attach the signature
327 CFRef
<CFDataRef
> signature
= CFDataCreateMutable(NULL
, 0);
328 CFTemp
<CFMutableDictionaryRef
> arguments("{%O=%O, %O=#N, %O=%d}", kSecCodeSignerDetached
, signature
.get(), kSecCodeSignerIdentity
,
329 kSecCodeSignerDigestAlgorithm
, (matchFlags
& kAuthorityFlagWhitelistSHA256
) ? kSecCodeSignatureHashSHA256
: kSecCodeSignatureHashSHA1
);
330 // for modern whitelist entries, neuter the identifier since it may be derived from the filename
331 if (matchFlags
& kAuthorityFlagWhitelistSHA256
)
332 CFDictionaryAddValue(arguments
, kSecCodeSignerIdentifier
, CFSTR("ADHOC"));
333 CFRef
<SecCodeSignerRef
> signer
;
334 MacOSError::check(SecCodeSignerCreate(arguments
, (matchFlags
& kAuthorityFlagWhitelistV2
) ? kSecCSSignOpaque
: kSecCSSignV1
, &signer
.aref()));
335 MacOSError::check(SecCodeSignerAddSignature(signer
, code
, kSecCSDefaultFlags
));
336 MacOSError::check(SecCodeSetDetachedSignature(code
, signature
, kSecCSDefaultFlags
));
338 SecRequirementRef dr
= NULL
;
339 SecCodeCopyDesignatedRequirement(code
, kSecCSDefaultFlags
, &dr
);
340 CFStringRef drs
= NULL
;
341 SecRequirementCopyString(dr
, kSecCSDefaultFlags
, &drs
);
342 secnotice("gk", "successfully created temporary signature - requirement=%s", cfString(drs
).c_str());
344 // if we're in GKE recording mode, save that signature and report its location
345 if (SYSPOLICY_RECORDER_MODE_ENABLED()) {
346 int status
= recorder_code_unable
; // ephemeral signature (not recorded)
347 if (geteuid() == 0) {
348 CFRef
<CFUUIDRef
> uuid
= CFUUIDCreate(NULL
);
349 std::string sigfile
= RECORDER_DIR
+ cfStringRelease(CFUUIDCreateString(NULL
, uuid
)) + ".tsig";
351 UnixPlusPlus::AutoFileDesc
fd(sigfile
, O_WRONLY
| O_CREAT
);
352 fd
.write(CFDataGetBytePtr(signature
), CFDataGetLength(signature
));
353 status
= recorder_code_adhoc
; // recorded signature
354 SYSPOLICY_RECORDER_MODE_ADHOC_PATH(cfString(path
).c_str(), type
, sigfile
.c_str());
358 // now report the D probe itself
359 CFRef
<CFDictionaryRef
> info
;
360 MacOSError::check(SecCodeCopySigningInformation(code
, kSecCSDefaultFlags
, &info
.aref()));
361 CFDataRef cdhash
= CFDataRef(CFDictionaryGetValue(info
, kSecCodeInfoUnique
));
362 SYSPOLICY_RECORDER_MODE(cfString(path
).c_str(), type
, "",
363 cdhash
? CFDataGetBytePtr(cdhash
) : NULL
, status
);
366 return true; // it worked; we're now (well) signed
375 // Read from disk, evaluate properly, cache as indicated.
377 void PolicyEngine::evaluateCode(CFURLRef path
, AuthorityType type
, SecAssessmentFlags flags
, CFDictionaryRef context
, CFMutableDictionaryRef result
, bool handleUnsigned
)
379 // not really a Gatekeeper function... but reject all "hard quarantined" files because they were made from sandboxed sources without download privilege
380 if (type
== kAuthorityExecute
) {
381 FileQuarantine
qtn(cfString(path
).c_str());
382 if (qtn
.flag(QTN_FLAG_HARD
))
383 MacOSError::throwMe(errSecCSFileHardQuarantined
);
386 // hack: if caller passed a UTI, use that to turn off app-only checks for some well-known ones
388 if (CFStringRef uti
= CFStringRef(CFDictionaryGetValue(context
, kSecAssessmentContextKeyUTI
))) {
389 appOk
= CFEqual(uti
, CFSTR("com.apple.systempreference.prefpane"))
390 || CFEqual(uti
, CFSTR("com.apple.systempreference.screen-saver"))
391 || CFEqual(uti
, CFSTR("com.apple.systempreference.screen-slide-saver"))
392 || CFEqual(uti
, CFSTR("com.apple.menu-extra"));
395 CFCopyRef
<SecStaticCodeRef
> code
;
396 MacOSError::check(SecStaticCodeCreateWithPath(path
, kSecCSDefaultFlags
| kSecCSForceOnlineNotarizationCheck
, &code
.aref()));
398 SecCSFlags validationFlags
= kSecCSEnforceRevocationChecks
| kSecCSCheckAllArchitectures
;
399 if (!(flags
& kSecAssessmentFlagAllowWeak
))
400 validationFlags
|= kSecCSStrictValidate
;
401 adjustValidation(code
);
403 // deal with a very special case (broken 10.6/10.7 Applet bundles)
404 OSStatus rc
= SecStaticCodeCheckValidity(code
, validationFlags
| kSecCSBasicValidateOnly
, NULL
);
405 if (rc
== errSecCSSignatureFailed
) {
406 if (!codeInvalidityExceptions(code
, result
)) { // invalidly signed, no exceptions -> error
407 if (SYSPOLICY_ASSESS_OUTCOME_BROKEN_ENABLED())
408 SYSPOLICY_ASSESS_OUTCOME_BROKEN(cfString(path
).c_str(), type
, false);
409 MacOSError::throwMe(rc
);
411 // recognized exception - treat as unsigned
412 if (SYSPOLICY_ASSESS_OUTCOME_BROKEN_ENABLED())
413 SYSPOLICY_ASSESS_OUTCOME_BROKEN(cfString(path
).c_str(), type
, true);
414 rc
= errSecCSUnsigned
;
417 // ad-hoc sign unsigned code
418 bool wasAdhocSigned
= false;
419 if (rc
== errSecCSUnsigned
&& handleUnsigned
&& (!overrideAssessment(flags
) || SYSPOLICY_RECORDER_MODE_ENABLED())) {
420 if (temporarySigning(code
, type
, path
, 0)) {
421 wasAdhocSigned
= true;
422 rc
= errSecSuccess
; // clear unsigned; we are now well-signed
423 validationFlags
|= kSecCSBasicValidateOnly
; // no need to re-validate deep contents
427 // prepare for deep traversal of (hopefully) good signatures
428 SecAssessmentFeedback feedback
= SecAssessmentFeedback(CFDictionaryGetValue(context
, kSecAssessmentContextKeyFeedback
));
429 __block CFRef
<CFMutableDictionaryRef
> nestedFailure
= NULL
; // save a nested failure for later
430 MacOSError::check(SecStaticCodeSetCallback(code
, kSecCSDefaultFlags
, NULL
, ^CFTypeRef (SecStaticCodeRef item
, CFStringRef cfStage
, CFDictionaryRef info
) {
431 string stage
= cfString(cfStage
);
432 if (stage
== "prepared") {
433 if (!CFEqual(item
, code
)) // genuine nested (not top) code
434 adjustValidation(item
);
435 } else if (stage
== "progress") {
436 if (feedback
&& CFEqual(item
, code
)) { // top level progress
437 bool proceed
= feedback(kSecAssessmentFeedbackProgress
, info
);
439 SecStaticCodeCancelValidation(code
, kSecCSDefaultFlags
);
441 } else if (stage
== "validated") {
442 SecStaticCodeSetCallback(item
, kSecCSDefaultFlags
, NULL
, NULL
); // clear callback to avoid unwanted recursion
443 evaluateCodeItem(item
, path
, type
, flags
, item
!= code
, result
);
444 if (CFTypeRef verdict
= CFDictionaryGetValue(result
, kSecAssessmentAssessmentVerdict
))
445 if (CFEqual(verdict
, kCFBooleanFalse
)) {
447 return makeCFNumber(OSStatus(errSecCSVetoed
)); // (signal nested-code policy failure, picked up below)
448 // nested code policy failure; save, reset, and continue
450 nestedFailure
= CFMutableDictionaryRef(CFDictionaryGetValue(result
, kSecAssessmentAssessmentAuthority
));
451 CFDictionaryRemoveValue(result
, kSecAssessmentAssessmentAuthority
);
452 CFDictionaryRemoveValue(result
, kSecAssessmentAssessmentVerdict
);
459 SecCSFlags topFlags
= validationFlags
| kSecCSCheckNestedCode
| kSecCSRestrictSymlinks
| kSecCSReportProgress
;
460 if (type
== kAuthorityExecute
&& !appOk
)
461 topFlags
|= kSecCSRestrictToAppLike
;
462 switch (rc
= SecStaticCodeCheckValidity(code
, topFlags
, NULL
)) {
463 case errSecSuccess
: // continue below
465 case errSecCSUnsigned
:
466 cfadd(result
, "{%O=#F}", kSecAssessmentAssessmentVerdict
);
467 addAuthority(flags
, result
, "no usable signature");
469 case errSecCSVetoed
: // nested code rejected by rule book; result was filled out there
471 addToAuthority(result
, kSecAssessmentAssessmentSource
, CFSTR("no usable signature")); // ad-hoc signature proved useless
473 case errSecCSWeakResourceRules
:
474 case errSecCSWeakResourceEnvelope
:
475 case errSecCSResourceNotSupported
:
476 case errSecCSAmbiguousBundleFormat
:
477 case errSecCSSignatureNotVerifiable
:
478 case errSecCSRegularFile
:
479 case errSecCSBadMainExecutable
:
480 case errSecCSBadFrameworkVersion
:
481 case errSecCSUnsealedAppRoot
:
482 case errSecCSUnsealedFrameworkRoot
:
483 case errSecCSInvalidSymlink
:
484 case errSecCSNotAppLike
:
486 // consult the whitelist
489 // we've bypassed evaluateCodeItem before we failed validation. Explicitly apply it now
490 SecStaticCodeSetCallback(code
, kSecCSDefaultFlags
, NULL
, NULL
);
491 evaluateCodeItem(code
, path
, type
, flags
| kSecAssessmentFlagNoCache
, false, result
);
492 if (CFTypeRef verdict
= CFDictionaryGetValue(result
, kSecAssessmentAssessmentVerdict
)) {
493 // verdict rendered from a nested component - signature not acceptable to Gatekeeper
494 if (CFEqual(verdict
, kCFBooleanFalse
)) // nested code rejected by rule book; result was filled out there
496 if (CFEqual(verdict
, kCFBooleanTrue
) && !(flags
& kSecAssessmentFlagIgnoreWhitelist
))
497 if (opaqueWhiteListContains(code
, feedback
, rc
)) {
502 label
= "allowed cdhash";
504 CFDictionaryReplaceValue(result
, kSecAssessmentAssessmentVerdict
, kCFBooleanFalse
);
505 label
= "obsolete resource envelope";
507 cfadd(result
, "{%O=%d}", kSecAssessmentAssessmentCodeSigningError
, rc
);
508 addAuthority(flags
, result
, label
, 0, NULL
, true);
512 MacOSError::throwMe(rc
);
515 // Copy notarization date, if present, from code signing information
516 CFRef
<CFDictionaryRef
> info
;
517 OSStatus status
= SecCodeCopySigningInformation(code
, kSecCSInternalInformation
, &info
.aref());
518 if (status
== 0 && info
) {
519 CFDateRef date
= (CFDateRef
)CFDictionaryGetValue(info
, kSecCodeInfoNotarizationDate
);
521 cfadd(result
, "{%O=%O}", kSecAssessmentAssessmentNotarizationDate
, date
);
524 secerror("Unable to copy signing information: %d", (int)status
);
527 if (nestedFailure
&& CFEqual(CFDictionaryGetValue(result
, kSecAssessmentAssessmentVerdict
), kCFBooleanTrue
)) {
528 // structure intact, top level approved, nested code failed policy
529 CFMutableDictionaryRef authority
= CFMutableDictionaryRef(CFDictionaryGetValue(result
, kSecAssessmentAssessmentAuthority
));
530 uint64_t ruleFlags
= cfNumber
<uint64_t>(CFNumberRef(CFDictionaryGetValue(authority
, kSecAssessmentAssessmentAuthorityFlags
)));
531 if (ruleFlags
& kAuthorityFlagDefault
) {
532 // default rule requires positive match at each nested code - reinstate failure
533 CFDictionaryReplaceValue(result
, kSecAssessmentAssessmentVerdict
, kCFBooleanFalse
);
534 CFDictionaryReplaceValue(result
, kSecAssessmentAssessmentAuthority
, nestedFailure
);
541 // Installer archive.
542 // Hybrid policy: If we detect an installer signature, use and validate that.
543 // If we don't, check for a code signature instead.
545 void PolicyEngine::evaluateInstall(CFURLRef path
, SecAssessmentFlags flags
, CFDictionaryRef context
, CFMutableDictionaryRef result
)
547 const AuthorityType type
= kAuthorityInstall
;
549 // check for recent explicit approval, using a bookmark's FileResourceIdentifierKey
550 if (CFRef
<CFDataRef
> bookmark
= cfLoadFile(lastApprovedFile
)) {
552 if (CFRef
<CFURLRef
> url
= CFURLCreateByResolvingBookmarkData(NULL
, bookmark
,
553 kCFBookmarkResolutionWithoutUIMask
| kCFBookmarkResolutionWithoutMountingMask
, NULL
, NULL
, &stale
, NULL
))
554 if (CFRef
<CFDataRef
> savedIdent
= CFDataRef(CFURLCreateResourcePropertyForKeyFromBookmarkData(NULL
, kCFURLFileResourceIdentifierKey
, bookmark
)))
555 if (CFRef
<CFDateRef
> savedMod
= CFDateRef(CFURLCreateResourcePropertyForKeyFromBookmarkData(NULL
, kCFURLContentModificationDateKey
, bookmark
))) {
556 CFRef
<CFDataRef
> currentIdent
;
557 CFRef
<CFDateRef
> currentMod
;
558 if (CFURLCopyResourcePropertyForKey(path
, kCFURLFileResourceIdentifierKey
, ¤tIdent
.aref(), NULL
))
559 if (CFURLCopyResourcePropertyForKey(path
, kCFURLContentModificationDateKey
, ¤tMod
.aref(), NULL
))
560 if (CFEqual(savedIdent
, currentIdent
) && CFEqual(savedMod
, currentMod
)) {
561 cfadd(result
, "{%O=#T}", kSecAssessmentAssessmentVerdict
);
562 addAuthority(flags
, result
, "explicit preference");
568 Xar
xar(cfString(path
).c_str());
570 // follow the code signing path
571 evaluateCode(path
, type
, flags
, context
, result
, true);
575 SQLite3::int64 latentID
= 0; // first (highest priority) disabled matching ID
576 std::string latentLabel
; // ... and associated label, if any
577 if (!xar
.isSigned()) {
579 if (SYSPOLICY_ASSESS_OUTCOME_UNSIGNED_ENABLED())
580 SYSPOLICY_ASSESS_OUTCOME_UNSIGNED(cfString(path
).c_str(), type
);
581 cfadd(result
, "{%O=#F}", kSecAssessmentAssessmentVerdict
);
582 addAuthority(flags
, result
, "no usable signature");
585 if (CFRef
<CFArrayRef
> certs
= xar
.copyCertChain()) {
586 CFRef
<CFTypeRef
> policy
= installerPolicy();
587 CFRef
<SecTrustRef
> trust
;
588 CFRef
<CFDataRef
> checksum
;
589 CFRef
<CFMutableDictionaryRef
> requirementContext
= makeCFMutableDictionary();
590 MacOSError::check(SecTrustCreateWithCertificates(certs
, policy
, &trust
.aref()));
591 // MacOSError::check(SecTrustSetAnchorCertificates(trust, cfEmptyArray())); // no anchors
592 MacOSError::check(SecTrustSetOptions(trust
, kSecTrustOptionAllowExpired
| kSecTrustOptionImplicitAnchors
));
594 SecTrustResultType trustResult
;
595 MacOSError::check(SecTrustEvaluate(trust
, &trustResult
));
596 CFRef
<CFArrayRef
> chain
;
597 CSSM_TP_APPLE_EVIDENCE_INFO
*info
;
598 MacOSError::check(SecTrustGetResult(trust
, &trustResult
, &chain
.aref(), &info
));
600 if (flags
& kSecAssessmentFlagRequestOrigin
)
601 setOrigin(chain
, result
);
603 switch (trustResult
) {
604 case kSecTrustResultProceed
:
605 case kSecTrustResultUnspecified
:
610 MacOSError::check(SecTrustGetCssmResultCode(trust
, &rc
));
611 MacOSError::throwMe(rc
);
615 xar
.registerStapledNotarization();
616 checksum
.take(xar
.createPackageChecksum());
618 double notarizationDate
= NAN
;
620 // Force a single online check for the checksum, which is always SHA1.
621 bool is_revoked
= checkNotarizationServiceForRevocation(checksum
, kSecCodeSignatureHashSHA1
, ¬arizationDate
);
623 MacOSError::throwMe(errSecCSRevokedNotarization
);
626 // Create the appropriate requirement context entry to allow notarized requirement check.
627 CFRef
<CFNumberRef
> algorithm
= makeCFNumber((uint32_t)xar
.checksumDigestAlgorithm());
628 cfadd(requirementContext
, "{%O=%O}", kSecRequirementKeyPackageChecksum
, checksum
.get());
629 cfadd(requirementContext
, "{%O=%O}", kSecRequirementKeyChecksumAlgorithm
, algorithm
.get());
631 if (!isnan(notarizationDate
)) {
632 CFRef
<CFDateRef
> date
= CFDateCreate(NULL
, notarizationDate
);
634 cfadd(result
, "{%O=%O}", kSecAssessmentAssessmentNotarizationDate
, date
.get());
639 SQLite::Statement
query(*this,
640 "SELECT allow, requirement, id, label, flags, disabled FROM scan_authority"
641 " WHERE type = :type"
642 " ORDER BY priority DESC;");
643 query
.bind(":type").integer(type
);
644 while (query
.nextRow()) {
645 bool allow
= int(query
[0]);
646 const char *reqString
= query
[1];
647 SQLite3::int64 id
= query
[2];
648 const char *label
= query
[3];
649 //sqlite_uint64 ruleFlags = query[4];
650 SQLite3::int64 disabled
= query
[5];
652 CFRef
<SecRequirementRef
> requirement
;
653 MacOSError::check(SecRequirementCreateWithString(CFTempString(reqString
), kSecCSDefaultFlags
, &requirement
.aref()));
654 switch (OSStatus rc
= SecRequirementEvaluate(requirement
, chain
, requirementContext
.get(), kSecCSDefaultFlags
)) {
655 case errSecSuccess
: // success
657 case errSecCSReqFailed
: // requirement missed, but otherwise okay
659 default: // broken in some way; all tests will fail like this so bail out
660 MacOSError::throwMe(rc
);
668 continue; // the loop
671 if (SYSPOLICY_ASSESS_OUTCOME_ACCEPT_ENABLED() || SYSPOLICY_ASSESS_OUTCOME_DENY_ENABLED()) {
673 SYSPOLICY_ASSESS_OUTCOME_ACCEPT(cfString(path
).c_str(), type
, label
, NULL
);
675 SYSPOLICY_ASSESS_OUTCOME_DENY(cfString(path
).c_str(), type
, label
, NULL
);
678 // not adding to the object cache - we could, but it's not likely to be worth it
679 cfadd(result
, "{%O=%B}", kSecAssessmentAssessmentVerdict
, allow
);
680 addAuthority(flags
, result
, label
, id
);
684 if (SYSPOLICY_ASSESS_OUTCOME_DEFAULT_ENABLED())
685 SYSPOLICY_ASSESS_OUTCOME_DEFAULT(cfString(path
).c_str(), type
, latentLabel
.c_str(), NULL
);
687 // no applicable authority. Deny by default
688 cfadd(result
, "{%O=#F}", kSecAssessmentAssessmentVerdict
);
689 addAuthority(flags
, result
, latentLabel
.c_str(), latentID
);
694 // Create a suitable policy array for verification of installer signatures.
696 static SecPolicyRef
makeRevocationPolicy()
698 CFRef
<SecPolicyRef
> policy(SecPolicyCreateRevocation(kSecRevocationUseAnyAvailableMethod
));
699 return policy
.yield();
702 static CFTypeRef
installerPolicy()
704 CFRef
<SecPolicyRef
> base
= SecPolicyCreateBasicX509();
705 CFRef
<SecPolicyRef
> revoc
= makeRevocationPolicy();
706 return makeCFArray(2, base
.get(), revoc
.get());
711 // LaunchServices-layer document open.
712 // We don't cache those at present. If we ever do, we need to authenticate CoreServicesUIAgent as the source of its risk assessment.
714 void PolicyEngine::evaluateDocOpen(CFURLRef path
, SecAssessmentFlags flags
, CFDictionaryRef context
, CFMutableDictionaryRef result
)
717 FileQuarantine
qtn(cfString(path
).c_str());
718 if (CFDictionaryGetValue(context
, kSecAssessmentContextKeyPrimarySignature
) == kCFBooleanTrue
) {
719 // Client requests that we focus on the code signature on this document and report on that.
720 // On this path, we care about the (code) signature on the document, not its risk assessment,
721 // and any exception is reported as a primary error.
722 if (qtn
.flag(QTN_FLAG_ASSESSMENT_OK
)) {
723 // previously added by user - hacked to say no/no usable signature to trigger proper DMG processing in XProtect
724 cfadd(result
, "{%O=#F}", kSecAssessmentAssessmentVerdict
);
725 addAuthority(flags
, result
, "no usable signature");
728 evaluateCode(path
, kAuthorityOpenDoc
, flags
, context
, result
, true);
731 if (CFStringRef riskCategory
= CFStringRef(CFDictionaryGetValue(context
, kLSDownloadRiskCategoryKey
))) {
733 if (CFEqual(riskCategory
, kLSRiskCategorySafe
)
734 || CFEqual(riskCategory
, kLSRiskCategoryNeutral
)
735 || CFEqual(riskCategory
, kLSRiskCategoryUnknown
)
736 || CFEqual(riskCategory
, kLSRiskCategoryMayContainUnsafeExecutable
)) {
737 cfadd(result
, "{%O=#T}", kSecAssessmentAssessmentVerdict
);
738 addAuthority(flags
, result
, "_XProtect");
739 } else if (qtn
.flag(QTN_FLAG_HARD
)) {
740 MacOSError::throwMe(errSecCSFileHardQuarantined
);
741 } else if (qtn
.flag(QTN_FLAG_ASSESSMENT_OK
)) {
742 // previously added by user
743 cfadd(result
, "{%O=#T}", kSecAssessmentAssessmentVerdict
);
744 addAuthority(flags
, result
, "Prior Assessment");
745 } else if (!overrideAssessment(flags
)) { // no need to do more work if we're off
747 evaluateCode(path
, kAuthorityOpenDoc
, flags
, context
, result
, true);
749 // some documents can't be code signed, so this may be quite benign
752 if (CFDictionaryGetValue(result
, kSecAssessmentAssessmentVerdict
) == NULL
) { // no code signature to help us out
753 cfadd(result
, "{%O=#F}", kSecAssessmentAssessmentVerdict
);
754 addAuthority(flags
, result
, "_XProtect");
756 addToAuthority(result
, kLSDownloadRiskCategoryKey
, riskCategory
);
760 // insufficient information from LS - deny by default
761 cfadd(result
, "{%O=#F}", kSecAssessmentAssessmentVerdict
);
762 addAuthority(flags
, result
, "Insufficient Context");
767 // Result-creation helpers
769 void PolicyEngine::addAuthority(SecAssessmentFlags flags
, CFMutableDictionaryRef parent
, const char *label
, SQLite::int64 row
, CFTypeRef cacheInfo
, bool weak
, uint64_t ruleFlags
)
771 CFRef
<CFMutableDictionaryRef
> auth
= makeCFMutableDictionary();
772 if (label
&& label
[0])
773 cfadd(auth
, "{%O=%s}", kSecAssessmentAssessmentSource
, label
);
775 CFDictionaryAddValue(auth
, kSecAssessmentAssessmentAuthorityRow
, CFTempNumber(row
));
776 if (overrideAssessment(flags
))
777 CFDictionaryAddValue(auth
, kSecAssessmentAssessmentAuthorityOverride
, kDisabledOverride
);
779 CFDictionaryAddValue(auth
, kSecAssessmentAssessmentFromCache
, cacheInfo
);
780 CFDictionaryAddValue(auth
, kSecAssessmentAssessmentAuthorityFlags
, CFTempNumber(ruleFlags
));
782 CFDictionaryAddValue(auth
, kSecAssessmentAssessmentWeakSignature
, kCFBooleanTrue
);
783 CFDictionaryReplaceValue(parent
, kSecAssessmentAssessmentAuthority
, auth
);
785 CFDictionaryAddValue(parent
, kSecAssessmentAssessmentAuthority
, auth
);
789 void PolicyEngine::addToAuthority(CFMutableDictionaryRef parent
, CFStringRef key
, CFTypeRef value
)
791 CFMutableDictionaryRef authority
= CFMutableDictionaryRef(CFDictionaryGetValue(parent
, kSecAssessmentAssessmentAuthority
));
793 CFDictionaryAddValue(authority
, key
, value
);
798 // Add a rule to the policy database
800 CFDictionaryRef
PolicyEngine::add(CFTypeRef inTarget
, AuthorityType type
, SecAssessmentFlags flags
, CFDictionaryRef context
)
802 // default type to execution
803 if (type
== kAuthorityInvalid
)
804 type
= kAuthorityExecute
;
806 authorizeUpdate(flags
, context
);
807 CFDictionary
ctx(context
, errSecCSInvalidAttributeValues
);
808 CFCopyRef
<CFTypeRef
> target
= inTarget
;
809 CFRef
<CFDataRef
> bookmark
= NULL
;
810 std::string filter_unsigned
;
813 case kAuthorityExecute
:
814 normalizeTarget(target
, type
, ctx
, &filter_unsigned
);
815 // bookmarks are untrusted and just a hint to callers
816 bookmark
= ctx
.get
<CFDataRef
>(kSecAssessmentRuleKeyBookmark
);
818 case kAuthorityInstall
:
819 if (inTarget
&& CFGetTypeID(inTarget
) == CFURLGetTypeID()) {
820 // no good way to turn an installer file into a requirement. Pretend to succeeed so caller proceeds
821 CFRef
<CFArrayRef
> properties
= makeCFArray(2, kCFURLFileResourceIdentifierKey
, kCFURLContentModificationDateKey
);
822 CFRef
<CFErrorRef
> error
;
823 CFURLBookmarkCreationOptions options
= kCFURLBookmarkCreationDoNotIncludeSandboxExtensionsMask
| kCFURLBookmarkCreationMinimalBookmarkMask
;
824 if (CFRef
<CFDataRef
> bookmark
= CFURLCreateBookmarkData(NULL
, CFURLRef(inTarget
), options
, properties
, NULL
, &error
.aref())) {
825 UnixPlusPlus::AutoFileDesc
fd(lastApprovedFile
, O_WRONLY
| O_CREAT
| O_TRUNC
);
826 fd
.write(CFDataGetBytePtr(bookmark
), CFDataGetLength(bookmark
));
831 case kAuthorityOpenDoc
:
832 // handle document-open differently: use quarantine flags for whitelisting
833 if (!target
|| CFGetTypeID(target
) != CFURLGetTypeID()) // can only "add" file paths
834 MacOSError::throwMe(errSecCSInvalidObjectRef
);
836 std::string spath
= cfString(target
.as
<CFURLRef
>());
837 FileQuarantine
qtn(spath
.c_str());
838 qtn
.setFlag(QTN_FLAG_ASSESSMENT_OK
);
839 qtn
.applyTo(spath
.c_str());
840 } catch (const CommonError
&error
) {
841 // could not set quarantine flag - report qualified success
842 return cfmake
<CFDictionaryRef
>("{%O=%O,'assessment:error'=%d}",
843 kSecAssessmentAssessmentAuthorityOverride
, CFSTR("error setting quarantine"), error
.osStatus());
845 return cfmake
<CFDictionaryRef
>("{%O=%O}", kSecAssessmentAssessmentAuthorityOverride
, CFSTR("unable to set quarantine"));
850 // if we now have anything else, we're busted
851 if (!target
|| CFGetTypeID(target
) != SecRequirementGetTypeID())
852 MacOSError::throwMe(errSecCSInvalidObjectRef
);
857 double expires
= never
;
859 SQLite::uint64 dbFlags
= kAuthorityFlagWhitelistV2
| kAuthorityFlagWhitelistSHA256
;
861 if (CFNumberRef pri
= ctx
.get
<CFNumberRef
>(kSecAssessmentUpdateKeyPriority
))
862 CFNumberGetValue(pri
, kCFNumberDoubleType
, &priority
);
863 if (CFStringRef lab
= ctx
.get
<CFStringRef
>(kSecAssessmentUpdateKeyLabel
))
864 label
= cfString(lab
);
865 if (CFDateRef time
= ctx
.get
<CFDateRef
>(kSecAssessmentUpdateKeyExpires
))
866 // we're using Julian dates here; convert from CFDate
867 expires
= dateToJulian(time
);
868 if (CFBooleanRef allowing
= ctx
.get
<CFBooleanRef
>(kSecAssessmentUpdateKeyAllow
))
869 allow
= allowing
== kCFBooleanTrue
;
870 if (CFStringRef rem
= ctx
.get
<CFStringRef
>(kSecAssessmentUpdateKeyRemarks
))
871 remarks
= cfString(rem
);
873 CFRef
<CFStringRef
> requirementText
;
874 MacOSError::check(SecRequirementCopyString(target
.as
<SecRequirementRef
>(), kSecCSDefaultFlags
, &requirementText
.aref()));
875 SQLite::Transaction
xact(*this, SQLite3::Transaction::deferred
, "add_rule");
876 SQLite::Statement
insert(*this,
877 "INSERT INTO authority (type, allow, requirement, priority, label, expires, filter_unsigned, remarks, flags)"
878 " VALUES (:type, :allow, :requirement, :priority, :label, :expires, :filter_unsigned, :remarks, :flags);");
879 insert
.bind(":type").integer(type
);
880 insert
.bind(":allow").integer(allow
);
881 insert
.bind(":requirement") = requirementText
.get();
882 insert
.bind(":priority") = priority
;
884 insert
.bind(":label") = label
;
885 insert
.bind(":expires") = expires
;
886 insert
.bind(":filter_unsigned") = filter_unsigned
.empty() ? NULL
: filter_unsigned
.c_str();
887 if (!remarks
.empty())
888 insert
.bind(":remarks") = remarks
;
889 insert
.bind(":flags").integer(dbFlags
);
891 SQLite::int64 newRow
= this->lastInsert();
893 SQLite::Statement
bi(*this, "INSERT INTO bookmarkhints (bookmark, authority) VALUES (:bookmark, :authority)");
894 bi
.bind(":bookmark") = CFDataRef(bookmark
);
895 bi
.bind(":authority").integer(newRow
);
898 this->purgeObjects(priority
);
900 notify_post(kNotifySecAssessmentUpdate
);
901 return cfmake
<CFDictionaryRef
>("{%O=%d}", kSecAssessmentUpdateKeyRow
, newRow
);
905 CFDictionaryRef
PolicyEngine::remove(CFTypeRef target
, AuthorityType type
, SecAssessmentFlags flags
, CFDictionaryRef context
)
907 if (type
== kAuthorityOpenDoc
) {
908 // handle document-open differently: use quarantine flags for whitelisting
909 authorizeUpdate(flags
, context
);
910 if (!target
|| CFGetTypeID(target
) != CFURLGetTypeID())
911 MacOSError::throwMe(errSecCSInvalidObjectRef
);
912 std::string spath
= cfString(CFURLRef(target
)).c_str();
913 FileQuarantine
qtn(spath
.c_str());
914 qtn
.clearFlag(QTN_FLAG_ASSESSMENT_OK
);
915 qtn
.applyTo(spath
.c_str());
918 return manipulateRules("DELETE FROM authority", target
, type
, flags
, context
, true);
921 CFDictionaryRef
PolicyEngine::enable(CFTypeRef target
, AuthorityType type
, SecAssessmentFlags flags
, CFDictionaryRef context
, bool authorize
)
923 return manipulateRules("UPDATE authority SET disabled = 0", target
, type
, flags
, context
, authorize
);
926 CFDictionaryRef
PolicyEngine::disable(CFTypeRef target
, AuthorityType type
, SecAssessmentFlags flags
, CFDictionaryRef context
, bool authorize
)
928 return manipulateRules("UPDATE authority SET disabled = 1", target
, type
, flags
, context
, authorize
);
931 CFDictionaryRef
PolicyEngine::find(CFTypeRef target
, AuthorityType type
, SecAssessmentFlags flags
, CFDictionaryRef context
)
933 //for privacy reasons we only want to allow the admin to list the database
934 authorizeUpdate(flags
, context
);
936 SQLite::Statement
query(*this);
937 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",
938 "scan_authority", target
, type
, flags
, context
,
939 " ORDER BY priority DESC");
940 CFRef
<CFMutableArrayRef
> found
= makeCFMutableArray(0);
941 while (query
.nextRow()) {
942 SQLite::int64 id
= query
[0];
943 int type
= int(query
[1]);
944 const char *requirement
= query
[2];
945 int allow
= int(query
[3]);
946 const char *label
= query
[4];
947 double priority
= query
[5];
948 const char *remarks
= query
[6];
949 double expires
= query
[7];
950 int disabled
= int(query
[8]);
951 CFRef
<CFDataRef
> bookmark
= query
[9].data();
952 CFRef
<CFMutableDictionaryRef
> rule
= makeCFMutableDictionary(5,
953 kSecAssessmentRuleKeyID
, CFTempNumber(id
).get(),
954 kSecAssessmentRuleKeyType
, CFRef
<CFStringRef
>(typeNameFor(type
)).get(),
955 kSecAssessmentRuleKeyRequirement
, CFTempString(requirement
).get(),
956 kSecAssessmentRuleKeyAllow
, allow
? kCFBooleanTrue
: kCFBooleanFalse
,
957 kSecAssessmentRuleKeyPriority
, CFTempNumber(priority
).get()
960 CFDictionaryAddValue(rule
, kSecAssessmentRuleKeyLabel
, CFTempString(label
));
962 CFDictionaryAddValue(rule
, kSecAssessmentRuleKeyRemarks
, CFTempString(remarks
));
963 if (expires
!= never
)
964 CFDictionaryAddValue(rule
, kSecAssessmentRuleKeyExpires
, CFRef
<CFDateRef
>(julianToDate(expires
)));
966 CFDictionaryAddValue(rule
, kSecAssessmentRuleKeyDisabled
, CFTempNumber(disabled
));
968 CFDictionaryAddValue(rule
, kSecAssessmentRuleKeyBookmark
, bookmark
);
969 CFArrayAppendValue(found
, rule
);
971 if (CFArrayGetCount(found
) == 0)
972 MacOSError::throwMe(errSecCSNoMatches
);
973 return cfmake
<CFDictionaryRef
>("{%O=%O}", kSecAssessmentUpdateKeyFound
, found
.get());
977 CFDictionaryRef
PolicyEngine::update(CFTypeRef target
, SecAssessmentFlags flags
, CFDictionaryRef context
)
980 installExplicitSet(gkeAuthFile
, gkeSigsFile
);
982 AuthorityType type
= typeFor(context
, kAuthorityInvalid
);
983 CFStringRef edit
= CFStringRef(CFDictionaryGetValue(context
, kSecAssessmentContextKeyUpdate
));
984 CFDictionaryRef result
;
985 if (CFEqual(edit
, kSecAssessmentUpdateOperationAdd
))
986 result
= this->add(target
, type
, flags
, context
);
987 else if (CFEqual(edit
, kSecAssessmentUpdateOperationRemove
))
988 result
= this->remove(target
, type
, flags
, context
);
989 else if (CFEqual(edit
, kSecAssessmentUpdateOperationEnable
))
990 result
= this->enable(target
, type
, flags
, context
, true);
991 else if (CFEqual(edit
, kSecAssessmentUpdateOperationDisable
))
992 result
= this->disable(target
, type
, flags
, context
, true);
993 else if (CFEqual(edit
, kSecAssessmentUpdateOperationFind
))
994 result
= this->find(target
, type
, flags
, context
);
996 MacOSError::throwMe(errSecCSInvalidAttributeValues
);
998 result
= makeCFDictionary(0); // success, no details
1004 // Construct and prepare an SQL query on the authority table, operating on some set of existing authority records.
1005 // In essence, this appends a suitable WHERE clause to the stanza passed and prepares it on the statement given.
1007 void PolicyEngine::selectRules(SQLite::Statement
&action
, std::string phrase
, std::string table
,
1008 CFTypeRef inTarget
, AuthorityType type
, SecAssessmentFlags flags
, CFDictionaryRef context
, std::string suffix
/* = "" */)
1010 CFDictionary
ctx(context
, errSecCSInvalidAttributeValues
);
1011 CFCopyRef
<CFTypeRef
> target
= inTarget
;
1012 std::string filter_unsigned
; // ignored; used just to trigger ad-hoc signing
1013 normalizeTarget(target
, type
, ctx
, &filter_unsigned
);
1016 if (CFStringRef lab
= ctx
.get
<CFStringRef
>(kSecAssessmentUpdateKeyLabel
))
1017 label
= cfString(CFStringRef(lab
));
1020 if (label
.empty()) {
1021 if (type
== kAuthorityInvalid
) {
1022 action
.query(phrase
+ suffix
);
1024 action
.query(phrase
+ " WHERE " + table
+ ".type = :type" + suffix
);
1025 action
.bind(":type").integer(type
);
1027 } else { // have label
1028 if (type
== kAuthorityInvalid
) {
1029 action
.query(phrase
+ " WHERE " + table
+ ".label = :label" + suffix
);
1031 action
.query(phrase
+ " WHERE " + table
+ ".type = :type AND " + table
+ ".label = :label" + suffix
);
1032 action
.bind(":type").integer(type
);
1034 action
.bind(":label") = label
;
1036 } else if (CFGetTypeID(target
) == CFNumberGetTypeID()) {
1037 action
.query(phrase
+ " WHERE " + table
+ ".id = :id" + suffix
);
1038 action
.bind(":id").integer(cfNumber
<uint64_t>(target
.as
<CFNumberRef
>()));
1039 } else if (CFGetTypeID(target
) == SecRequirementGetTypeID()) {
1040 if (type
== kAuthorityInvalid
)
1041 type
= kAuthorityExecute
;
1042 CFRef
<CFStringRef
> requirementText
;
1043 MacOSError::check(SecRequirementCopyString(target
.as
<SecRequirementRef
>(), kSecCSDefaultFlags
, &requirementText
.aref()));
1044 action
.query(phrase
+ " WHERE " + table
+ ".type = :type AND " + table
+ ".requirement = :requirement" + suffix
);
1045 action
.bind(":type").integer(type
);
1046 action
.bind(":requirement") = requirementText
.get();
1048 MacOSError::throwMe(errSecCSInvalidObjectRef
);
1053 // Execute an atomic change to existing records in the authority table.
1055 CFDictionaryRef
PolicyEngine::manipulateRules(const std::string
&stanza
,
1056 CFTypeRef inTarget
, AuthorityType type
, SecAssessmentFlags flags
, CFDictionaryRef context
, bool authorize
)
1058 SQLite::Transaction
xact(*this, SQLite3::Transaction::deferred
, "rule_change");
1059 SQLite::Statement
action(*this);
1061 authorizeUpdate(flags
, context
);
1062 selectRules(action
, stanza
, "authority", inTarget
, type
, flags
, context
);
1064 unsigned int changes
= this->changes(); // latch change count
1065 // We MUST purge objects with priority <= MAX(priority of any changed rules);
1066 // but for now we just get lazy and purge them ALL.
1068 this->purgeObjects(1.0E100
);
1070 notify_post(kNotifySecAssessmentUpdate
);
1071 return cfmake
<CFDictionaryRef
>("{%O=%d}", kSecAssessmentUpdateKeyCount
, changes
);
1073 // no change; return an error
1074 MacOSError::throwMe(errSecCSNoMatches
);
1079 // Fill in extra information about the originator of cryptographic credentials found - if any
1081 void PolicyEngine::setOrigin(CFArrayRef chain
, CFMutableDictionaryRef result
)
1084 if (CFArrayGetCount(chain
) > 0)
1085 if (SecCertificateRef leaf
= SecCertificateRef(CFArrayGetValueAtIndex(chain
, 0)))
1086 if (CFStringRef summary
= SecCertificateCopyLongDescription(NULL
, leaf
, NULL
)) {
1087 CFDictionarySetValue(result
, kSecAssessmentAssessmentOriginator
, summary
);
1094 // Take an assessment outcome and record it in the object cache
1096 void PolicyEngine::recordOutcome(SecStaticCodeRef code
, bool allow
, AuthorityType type
, double expires
, SQLite::int64 authority
)
1098 CFRef
<CFDictionaryRef
> info
;
1099 MacOSError::check(SecCodeCopySigningInformation(code
, kSecCSDefaultFlags
, &info
.aref()));
1100 CFDataRef cdHash
= CFDataRef(CFDictionaryGetValue(info
, kSecCodeInfoUnique
));
1101 assert(cdHash
); // was signed
1102 CFRef
<CFURLRef
> path
;
1103 MacOSError::check(SecCodeCopyPath(code
, kSecCSDefaultFlags
, &path
.aref()));
1105 SQLite::Transaction
xact(*this, SQLite3::Transaction::deferred
, "caching");
1106 SQLite::Statement
insert(*this,
1107 "INSERT OR REPLACE INTO object (type, allow, hash, expires, path, authority)"
1108 " VALUES (:type, :allow, :hash, :expires, :path,"
1109 " CASE :authority WHEN 0 THEN (SELECT id FROM authority WHERE label = 'No Matching Rule') ELSE :authority END"
1111 insert
.bind(":type").integer(type
);
1112 insert
.bind(":allow").integer(allow
);
1113 insert
.bind(":hash") = cdHash
;
1114 insert
.bind(":expires") = expires
;
1115 insert
.bind(":path") = cfString(path
);
1116 insert
.bind(":authority").integer(authority
);
1123 // Record a UI failure record after proper validation of the caller
1125 void PolicyEngine::recordFailure(CFDictionaryRef info
)
1127 CFRef
<CFDataRef
> infoData
= makeCFData(info
);
1128 UnixPlusPlus::AutoFileDesc
fd(lastRejectFile
, O_WRONLY
| O_CREAT
| O_TRUNC
);
1129 fd
.write(CFDataGetBytePtr(infoData
), CFDataGetLength(infoData
));
1130 notify_post(kNotifySecAssessmentRecordingChange
);
1135 // Perform update authorization processing.
1136 // Throws an exception if authorization is denied.
1138 static void authorizeUpdate(SecAssessmentFlags flags
, CFDictionaryRef context
)
1140 AuthorizationRef authorization
= NULL
;
1143 if (CFTypeRef authkey
= CFDictionaryGetValue(context
, kSecAssessmentUpdateKeyAuthorization
))
1144 if (CFGetTypeID(authkey
) == CFDataGetTypeID()) {
1145 CFDataRef authdata
= CFDataRef(authkey
);
1146 if (CFDataGetLength(authdata
) != sizeof(AuthorizationExternalForm
))
1147 MacOSError::throwMe(errSecCSInvalidObjectRef
);
1148 MacOSError::check(AuthorizationCreateFromExternalForm((AuthorizationExternalForm
*)CFDataGetBytePtr(authdata
), &authorization
));
1150 if (authorization
== NULL
)
1151 MacOSError::throwMe(errSecCSDBDenied
);
1153 AuthorizationItem right
[] = {
1154 { "com.apple.security.assessment.update", 0, NULL
, 0 }
1156 AuthorizationRights rights
= { sizeof(right
) / sizeof(right
[0]), right
};
1157 MacOSError::check(AuthorizationCopyRights(authorization
, &rights
, NULL
,
1158 kAuthorizationFlagExtendRights
| kAuthorizationFlagInteractionAllowed
, NULL
));
1160 MacOSError::check(AuthorizationFree(authorization
, kAuthorizationFlagDefaults
));
1165 // Perform common argument normalizations for update operations
1167 void PolicyEngine::normalizeTarget(CFRef
<CFTypeRef
> &target
, AuthorityType type
, CFDictionary
&context
, std::string
*signUnsigned
)
1169 // turn CFURLs into (designated) SecRequirements
1170 if (target
&& CFGetTypeID(target
) == CFURLGetTypeID()) {
1171 CFRef
<SecStaticCodeRef
> code
;
1172 CFURLRef path
= target
.as
<CFURLRef
>();
1173 MacOSError::check(SecStaticCodeCreateWithPath(path
, kSecCSDefaultFlags
, &code
.aref()));
1174 switch (OSStatus rc
= SecCodeCopyDesignatedRequirement(code
, kSecCSDefaultFlags
, (SecRequirementRef
*)&target
.aref())) {
1175 case errSecSuccess
: {
1176 // use the *default* DR to avoid unreasonably wide DRs opening up Gatekeeper to attack
1177 CFRef
<CFDictionaryRef
> info
;
1178 MacOSError::check(SecCodeCopySigningInformation(code
, kSecCSRequirementInformation
, &info
.aref()));
1179 target
= CFDictionaryGetValue(info
, kSecCodeInfoImplicitDesignatedRequirement
);
1182 case errSecCSUnsigned
:
1183 if (signUnsigned
&& temporarySigning(code
, type
, path
, kAuthorityFlagWhitelistV2
| kAuthorityFlagWhitelistSHA256
)) { // ad-hoc sign the code temporarily
1184 MacOSError::check(SecCodeCopyDesignatedRequirement(code
, kSecCSDefaultFlags
, (SecRequirementRef
*)&target
.aref()));
1185 *signUnsigned
= createWhitelistScreen(code
);
1188 MacOSError::check(rc
);
1189 case errSecCSSignatureFailed
:
1190 // recover certain cases of broken signatures (well, try)
1191 if (codeInvalidityExceptions(code
, NULL
)) {
1192 // Ad-hoc sign the code in place (requiring a writable subject). This requires root privileges.
1193 CFRef
<SecCodeSignerRef
> signer
;
1194 CFTemp
<CFDictionaryRef
> arguments("{%O=#N}", kSecCodeSignerIdentity
);
1195 MacOSError::check(SecCodeSignerCreate(arguments
, kSecCSSignOpaque
, &signer
.aref()));
1196 MacOSError::check(SecCodeSignerAddSignature(signer
, code
, kSecCSDefaultFlags
));
1197 MacOSError::check(SecCodeCopyDesignatedRequirement(code
, kSecCSDefaultFlags
, (SecRequirementRef
*)&target
.aref()));
1200 MacOSError::check(rc
);
1202 MacOSError::check(rc
);
1204 if (context
.get(kSecAssessmentUpdateKeyRemarks
) == NULL
) {
1205 // no explicit remarks; add one with the path
1206 CFRef
<CFURLRef
> path
;
1207 MacOSError::check(SecCodeCopyPath(code
, kSecCSDefaultFlags
, &path
.aref()));
1208 CFMutableDictionaryRef dict
= makeCFMutableDictionary(context
.get());
1209 CFDictionaryAddValue(dict
, kSecAssessmentUpdateKeyRemarks
, CFTempString(cfString(path
)));
1212 CFStringRef edit
= CFStringRef(context
.get(kSecAssessmentContextKeyUpdate
));
1213 if (type
== kAuthorityExecute
&& CFEqual(edit
, kSecAssessmentUpdateOperationAdd
)) {
1214 // implicitly whitelist the code
1215 opaqueWhitelistAdd(code
);
1222 // Process special overrides for invalidly signed code.
1223 // This is the (hopefully minimal) concessions we make to keep hurting our customers
1224 // for our own prior mistakes...
1226 static bool codeInvalidityExceptions(SecStaticCodeRef code
, CFMutableDictionaryRef result
)
1228 CFRef
<CFDictionaryRef
> info
;
1229 MacOSError::check(SecCodeCopySigningInformation(code
, kSecCSDefaultFlags
, &info
.aref()));
1230 if (CFURLRef executable
= CFURLRef(CFDictionaryGetValue(info
, kSecCodeInfoMainExecutable
))) {
1232 if (OSAIsRecognizedExecutableURL(executable
, &error
)) {
1234 CFDictionaryAddValue(result
,
1235 kSecAssessmentAssessmentAuthorityOverride
, CFSTR("ignoring known invalid applet signature"));
1243 } // end namespace CodeSigning
1244 } // end namespace Security