2 * Copyright (c) 2011-2012 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>
38 #include <CoreServices/CoreServicesPriv.h>
39 #include "SecCodePriv.h"
40 #undef check // Macro! Yech.
43 #include <OpenScriptingUtilPriv.h>
48 namespace CodeSigning
{
50 static const double NEGATIVE_HOLD
= 60.0/86400; // 60 seconds to cache negative outcomes
52 static const char RECORDER_DIR
[] = "/tmp/gke-"; // recorder mode destination for detached signatures
54 recorder_code_untrusted
= 0, // signed but untrusted
55 recorder_code_adhoc
= 1, // unsigned; signature recorded
56 recorder_code_unable
= 2, // unsigned; unable to record signature
60 static void authorizeUpdate(SecAssessmentFlags flags
, CFDictionaryRef context
);
61 static void normalizeTarget(CFRef
<CFTypeRef
> &target
, CFDictionary
&context
, bool signUnsigned
= false);
62 static bool codeInvalidityExceptions(SecStaticCodeRef code
, CFMutableDictionaryRef result
);
63 static CFTypeRef
installerPolicy() CF_RETURNS_RETAINED
;
69 PolicyEngine::PolicyEngine()
70 : PolicyDatabase(NULL
, SQLITE_OPEN_READWRITE
| SQLITE_OPEN_CREATE
)
74 PolicyEngine::~PolicyEngine()
79 // Top-level evaluation driver
81 void PolicyEngine::evaluate(CFURLRef path
, AuthorityType type
, SecAssessmentFlags flags
, CFDictionaryRef context
, CFMutableDictionaryRef result
)
84 case kAuthorityExecute
:
85 evaluateCode(path
, kAuthorityExecute
, flags
, context
, result
);
87 case kAuthorityInstall
:
88 evaluateInstall(path
, flags
, context
, result
);
90 case kAuthorityOpenDoc
:
91 evaluateDocOpen(path
, flags
, context
, result
);
94 MacOSError::throwMe(errSecCSInvalidAttributeValues
);
102 // Read from disk, evaluate properly, cache as indicated. The whole thing, so far.
104 void PolicyEngine::evaluateCode(CFURLRef path
, AuthorityType type
, SecAssessmentFlags flags
, CFDictionaryRef context
, CFMutableDictionaryRef result
)
106 FileQuarantine
qtn(cfString(path
).c_str());
107 if (qtn
.flag(QTN_FLAG_HARD
))
108 MacOSError::throwMe(errSecCSFileHardQuarantined
);
110 CFRef
<SecStaticCodeRef
> code
;
111 MacOSError::check(SecStaticCodeCreateWithPath(path
, kSecCSDefaultFlags
, &code
.aref()));
113 const SecCSFlags validationFlags
= kSecCSEnforceRevocationChecks
;
115 SQLite::Statement
query(*this,
116 "SELECT allow, requirement, id, label, expires, flags, disabled FROM scan_authority"
117 " WHERE type = :type"
118 " ORDER BY priority DESC;");
119 query
.bind(":type").integer(type
);
120 SQLite3::int64 latentID
= 0; // first (highest priority) disabled matching ID
121 std::string latentLabel
; // ... and associated label, if any
122 while (query
.nextRow()) {
123 bool allow
= int(query
[0]);
124 const char *reqString
= query
[1];
125 SQLite3::int64 id
= query
[2];
126 const char *label
= query
[3];
127 double expires
= query
[4];
128 sqlite3_int64 ruleFlags
= query
[5];
129 SQLite3::int64 disabled
= query
[6];
131 CFRef
<SecRequirementRef
> requirement
;
132 MacOSError::check(SecRequirementCreateWithString(CFTempString(reqString
), kSecCSDefaultFlags
, &requirement
.aref()));
133 OSStatus rc
= SecStaticCodeCheckValidity(code
, validationFlags
, requirement
);
135 if (rc
== errSecCSUnsigned
&& !overrideAssessment()) {
137 // ad-hoc sign the code and attach the signature
138 CFRef
<CFDataRef
> signature
= CFDataCreateMutable(NULL
, 0);
139 CFTemp
<CFDictionaryRef
> arguments("{%O=%O, %O=#N}", kSecCodeSignerDetached
, signature
.get(), kSecCodeSignerIdentity
);
140 CFRef
<SecCodeSignerRef
> signer
;
141 MacOSError::check(SecCodeSignerCreate(arguments
, kSecCSDefaultFlags
, &signer
.aref()));
142 MacOSError::check(SecCodeSignerAddSignature(signer
, code
, kSecCSDefaultFlags
));
143 MacOSError::check(SecCodeSetDetachedSignature(code
, signature
, kSecCSDefaultFlags
));
145 // if we're in GKE recording mode, save that signature and report its location
146 if (SYSPOLICY_RECORDER_MODE_ENABLED()) {
147 int status
= recorder_code_unable
; // ephemeral signature (not recorded)
148 if (geteuid() == 0) {
149 CFRef
<CFUUIDRef
> uuid
= CFUUIDCreate(NULL
);
150 std::string sigfile
= RECORDER_DIR
+ cfStringRelease(CFUUIDCreateString(NULL
, uuid
)) + ".tsig";
152 UnixPlusPlus::AutoFileDesc
fd(sigfile
, O_WRONLY
| O_CREAT
);
153 fd
.write(CFDataGetBytePtr(signature
), CFDataGetLength(signature
));
154 status
= recorder_code_adhoc
; // recorded signature
155 SYSPOLICY_RECORDER_MODE_ADHOC_PATH(cfString(path
).c_str(), type
, sigfile
.c_str());
159 // now report the D probe itself
160 CFRef
<CFDictionaryRef
> info
;
161 MacOSError::check(SecCodeCopySigningInformation(code
, kSecCSDefaultFlags
, &info
.aref()));
162 CFDataRef cdhash
= CFDataRef(CFDictionaryGetValue(info
, kSecCodeInfoUnique
));
163 SYSPOLICY_RECORDER_MODE(cfString(path
).c_str(), type
, "",
164 cdhash
? CFDataGetBytePtr(cdhash
) : NULL
, status
);
167 // rerun the validation to update state
168 rc
= SecStaticCodeCheckValidity(code
, validationFlags
| kSecCSBasicValidateOnly
, requirement
);
173 case noErr
: // well signed and satisfies requirement...
174 break; // ... continue below
175 case errSecCSSignatureFailed
:
176 if (!codeInvalidityExceptions(code
, result
)) {
177 if (SYSPOLICY_ASSESS_OUTCOME_BROKEN_ENABLED())
178 SYSPOLICY_ASSESS_OUTCOME_BROKEN(cfString(path
).c_str(), type
, false);
179 MacOSError::throwMe(rc
);
181 if (SYSPOLICY_ASSESS_OUTCOME_BROKEN_ENABLED())
182 SYSPOLICY_ASSESS_OUTCOME_BROKEN(cfString(path
).c_str(), type
, true);
183 // treat as unsigned to fix problems in the field
184 case errSecCSUnsigned
:
185 cfadd(result
, "{%O=#F}", kSecAssessmentAssessmentVerdict
);
186 addAuthority(result
, "no usable signature");
188 case errSecCSReqFailed
: // requirement missed, but otherwise okay
190 default: // broken in some way; all tests will fail like this so bail out
191 MacOSError::throwMe(rc
);
199 continue; // the loop
202 CFRef
<CFDictionaryRef
> info
; // as needed
203 if (flags
& kSecAssessmentFlagRequestOrigin
) {
205 MacOSError::check(SecCodeCopySigningInformation(code
, kSecCSSigningInformation
, &info
.aref()));
206 if (CFArrayRef chain
= CFArrayRef(CFDictionaryGetValue(info
, kSecCodeInfoCertificates
)))
207 setOrigin(chain
, result
);
209 if (!(ruleFlags
& kAuthorityFlagInhibitCache
) && !(flags
& kSecAssessmentFlagNoCache
)) { // cache inhibit
211 MacOSError::check(SecCodeCopySigningInformation(code
, kSecCSSigningInformation
, &info
.aref()));
212 if (SecTrustRef trust
= SecTrustRef(CFDictionaryGetValue(info
, kSecCodeInfoTrust
))) {
213 CFRef
<CFDictionaryRef
> xinfo
;
214 MacOSError::check(SecTrustCopyExtendedResult(trust
, &xinfo
.aref()));
215 if (CFDateRef limit
= CFDateRef(CFDictionaryGetValue(xinfo
, kSecTrustExpirationDate
))) {
216 this->recordOutcome(code
, allow
, type
, min(expires
, dateToJulian(limit
)), id
);
221 if (SYSPOLICY_ASSESS_OUTCOME_ACCEPT_ENABLED()) {
223 MacOSError::check(SecCodeCopySigningInformation(code
, kSecCSSigningInformation
, &info
.aref()));
224 CFDataRef cdhash
= CFDataRef(CFDictionaryGetValue(info
, kSecCodeInfoUnique
));
225 SYSPOLICY_ASSESS_OUTCOME_ACCEPT(cfString(path
).c_str(), type
, label
, cdhash
? CFDataGetBytePtr(cdhash
) : NULL
);
228 if (SYSPOLICY_ASSESS_OUTCOME_DENY_ENABLED() || SYSPOLICY_RECORDER_MODE_ENABLED()) {
230 MacOSError::check(SecCodeCopySigningInformation(code
, kSecCSSigningInformation
, &info
.aref()));
231 CFDataRef cdhash
= CFDataRef(CFDictionaryGetValue(info
, kSecCodeInfoUnique
));
232 std::string cpath
= cfString(path
);
233 const void *hashp
= cdhash
? CFDataGetBytePtr(cdhash
) : NULL
;
234 SYSPOLICY_ASSESS_OUTCOME_DENY(cpath
.c_str(), type
, label
, hashp
);
235 SYSPOLICY_RECORDER_MODE(cpath
.c_str(), type
, label
, hashp
, recorder_code_untrusted
);
238 cfadd(result
, "{%O=%B}", kSecAssessmentAssessmentVerdict
, allow
);
239 addAuthority(result
, label
, id
);
243 // no applicable authority. Deny by default
244 CFRef
<CFDictionaryRef
> info
;
245 MacOSError::check(SecCodeCopySigningInformation(code
, kSecCSSigningInformation
, &info
.aref()));
246 if (flags
& kSecAssessmentFlagRequestOrigin
) {
247 if (CFArrayRef chain
= CFArrayRef(CFDictionaryGetValue(info
, kSecCodeInfoCertificates
)))
248 setOrigin(chain
, result
);
250 if (SYSPOLICY_ASSESS_OUTCOME_DEFAULT_ENABLED() || SYSPOLICY_RECORDER_MODE_ENABLED()) {
251 CFDataRef cdhash
= CFDataRef(CFDictionaryGetValue(info
, kSecCodeInfoUnique
));
252 const void *hashp
= cdhash
? CFDataGetBytePtr(cdhash
) : NULL
;
253 std::string cpath
= cfString(path
);
254 SYSPOLICY_ASSESS_OUTCOME_DEFAULT(cpath
.c_str(), type
, latentLabel
.c_str(), hashp
);
255 SYSPOLICY_RECORDER_MODE(cpath
.c_str(), type
, latentLabel
.c_str(), hashp
, 0);
257 if (!(flags
& kSecAssessmentFlagNoCache
))
258 this->recordOutcome(code
, false, type
, this->julianNow() + NEGATIVE_HOLD
, latentID
);
259 cfadd(result
, "{%O=%B}", kSecAssessmentAssessmentVerdict
, false);
260 addAuthority(result
, latentLabel
.c_str(), latentID
);
265 // Installer archive.
266 // Hybrid policy: If we detect an installer signature, use and validate that.
267 // If we don't, check for a code signature instead.
269 void PolicyEngine::evaluateInstall(CFURLRef path
, SecAssessmentFlags flags
, CFDictionaryRef context
, CFMutableDictionaryRef result
)
271 const AuthorityType type
= kAuthorityInstall
;
273 Xar
xar(cfString(path
).c_str());
275 // follow the code signing path
276 evaluateCode(path
, type
, flags
, context
, result
);
280 SQLite3::int64 latentID
= 0; // first (highest priority) disabled matching ID
281 std::string latentLabel
; // ... and associated label, if any
282 if (!xar
.isSigned()) {
284 if (SYSPOLICY_ASSESS_OUTCOME_UNSIGNED_ENABLED())
285 SYSPOLICY_ASSESS_OUTCOME_UNSIGNED(cfString(path
).c_str(), type
);
286 cfadd(result
, "{%O=%B}", kSecAssessmentAssessmentVerdict
, false);
287 addAuthority(result
, "no usable signature");
290 if (CFRef
<CFArrayRef
> certs
= xar
.copyCertChain()) {
291 CFRef
<CFTypeRef
> policy
= installerPolicy();
292 CFRef
<SecTrustRef
> trust
;
293 MacOSError::check(SecTrustCreateWithCertificates(certs
, policy
, &trust
.aref()));
294 // MacOSError::check(SecTrustSetAnchorCertificates(trust, cfEmptyArray())); // no anchors
295 MacOSError::check(SecTrustSetOptions(trust
, kSecTrustOptionAllowExpired
| kSecTrustOptionImplicitAnchors
));
297 SecTrustResultType trustResult
;
298 MacOSError::check(SecTrustEvaluate(trust
, &trustResult
));
299 CFRef
<CFArrayRef
> chain
;
300 CSSM_TP_APPLE_EVIDENCE_INFO
*info
;
301 MacOSError::check(SecTrustGetResult(trust
, &trustResult
, &chain
.aref(), &info
));
303 if (flags
& kSecAssessmentFlagRequestOrigin
)
304 setOrigin(chain
, result
);
306 switch (trustResult
) {
307 case kSecTrustResultProceed
:
308 case kSecTrustResultUnspecified
:
313 MacOSError::check(SecTrustGetCssmResultCode(trust
, &rc
));
314 MacOSError::throwMe(rc
);
318 SQLite::Statement
query(*this,
319 "SELECT allow, requirement, id, label, flags, disabled FROM scan_authority"
320 " WHERE type = :type"
321 " ORDER BY priority DESC;");
322 query
.bind(":type").integer(type
);
323 while (query
.nextRow()) {
324 bool allow
= int(query
[0]);
325 const char *reqString
= query
[1];
326 SQLite3::int64 id
= query
[2];
327 const char *label
= query
[3];
328 //sqlite_uint64 ruleFlags = query[4];
329 SQLite3::int64 disabled
= query
[5];
331 CFRef
<SecRequirementRef
> requirement
;
332 MacOSError::check(SecRequirementCreateWithString(CFTempString(reqString
), kSecCSDefaultFlags
, &requirement
.aref()));
333 switch (OSStatus rc
= SecRequirementEvaluate(requirement
, chain
, NULL
, kSecCSDefaultFlags
)) {
334 case noErr
: // success
336 case errSecCSReqFailed
: // requirement missed, but otherwise okay
338 default: // broken in some way; all tests will fail like this so bail out
339 MacOSError::throwMe(rc
);
347 continue; // the loop
350 if (SYSPOLICY_ASSESS_OUTCOME_ACCEPT_ENABLED() || SYSPOLICY_ASSESS_OUTCOME_DENY_ENABLED()) {
352 SYSPOLICY_ASSESS_OUTCOME_ACCEPT(cfString(path
).c_str(), type
, label
, NULL
);
354 SYSPOLICY_ASSESS_OUTCOME_DENY(cfString(path
).c_str(), type
, label
, NULL
);
357 // not adding to the object cache - we could, but it's not likely to be worth it
358 cfadd(result
, "{%O=%B}", kSecAssessmentAssessmentVerdict
, allow
);
359 addAuthority(result
, label
, id
);
363 if (SYSPOLICY_ASSESS_OUTCOME_DEFAULT_ENABLED())
364 SYSPOLICY_ASSESS_OUTCOME_DEFAULT(cfString(path
).c_str(), type
, latentLabel
.c_str(), NULL
);
366 // no applicable authority. Deny by default
367 cfadd(result
, "{%O=#F}", kSecAssessmentAssessmentVerdict
);
368 addAuthority(result
, latentLabel
.c_str(), latentID
);
373 // Create a suitable policy array for verification of installer signatures.
375 static SecPolicyRef
makeCRLPolicy()
377 CFRef
<SecPolicyRef
> policy
;
378 MacOSError::check(SecPolicyCopy(CSSM_CERT_X_509v3
, &CSSMOID_APPLE_TP_REVOCATION_CRL
, &policy
.aref()));
379 CSSM_APPLE_TP_CRL_OPTIONS options
;
380 memset(&options
, 0, sizeof(options
));
381 options
.Version
= CSSM_APPLE_TP_CRL_OPTS_VERSION
;
382 options
.CrlFlags
= CSSM_TP_ACTION_FETCH_CRL_FROM_NET
| CSSM_TP_ACTION_CRL_SUFFICIENT
;
383 CSSM_DATA optData
= { sizeof(options
), (uint8
*)&options
};
384 MacOSError::check(SecPolicySetValue(policy
, &optData
));
385 return policy
.yield();
388 static SecPolicyRef
makeOCSPPolicy()
390 CFRef
<SecPolicyRef
> policy
;
391 MacOSError::check(SecPolicyCopy(CSSM_CERT_X_509v3
, &CSSMOID_APPLE_TP_REVOCATION_OCSP
, &policy
.aref()));
392 CSSM_APPLE_TP_OCSP_OPTIONS options
;
393 memset(&options
, 0, sizeof(options
));
394 options
.Version
= CSSM_APPLE_TP_OCSP_OPTS_VERSION
;
395 options
.Flags
= CSSM_TP_ACTION_OCSP_SUFFICIENT
;
396 CSSM_DATA optData
= { sizeof(options
), (uint8
*)&options
};
397 MacOSError::check(SecPolicySetValue(policy
, &optData
));
398 return policy
.yield();
401 static CFTypeRef
installerPolicy()
403 CFRef
<SecPolicyRef
> base
= SecPolicyCreateBasicX509();
404 CFRef
<SecPolicyRef
> crl
= makeCRLPolicy();
405 CFRef
<SecPolicyRef
> ocsp
= makeOCSPPolicy();
406 return makeCFArray(3, base
.get(), crl
.get(), ocsp
.get());
411 // LaunchServices-layer document open.
412 // We don't cache those at present. If we ever do, we need to authenticate CoreServicesUIAgent as the source of its risk assessment.
414 void PolicyEngine::evaluateDocOpen(CFURLRef path
, SecAssessmentFlags flags
, CFDictionaryRef context
, CFMutableDictionaryRef result
)
417 if (CFStringRef riskCategory
= CFStringRef(CFDictionaryGetValue(context
, kLSDownloadRiskCategoryKey
))) {
418 FileQuarantine
qtn(cfString(path
).c_str());
420 if (CFEqual(riskCategory
, kLSRiskCategorySafe
)
421 || CFEqual(riskCategory
, kLSRiskCategoryNeutral
)
422 || CFEqual(riskCategory
, kLSRiskCategoryUnknown
)
423 || CFEqual(riskCategory
, kLSRiskCategoryMayContainUnsafeExecutable
)) {
424 cfadd(result
, "{%O=#T}", kSecAssessmentAssessmentVerdict
);
425 addAuthority(result
, "_XProtect");
426 } else if (qtn
.flag(QTN_FLAG_HARD
)) {
427 MacOSError::throwMe(errSecCSFileHardQuarantined
);
428 } else if (qtn
.flag(QTN_FLAG_ASSESSMENT_OK
)) {
429 cfadd(result
, "{%O=#T}", kSecAssessmentAssessmentVerdict
);
430 addAuthority(result
, "Prior Assessment");
432 cfadd(result
, "{%O=#F}", kSecAssessmentAssessmentVerdict
);
433 addAuthority(result
, "_XProtect");
435 addToAuthority(result
, kLSDownloadRiskCategoryKey
, riskCategory
);
439 // insufficient information from LS - deny by default
440 cfadd(result
, "{%O=#F}", kSecAssessmentAssessmentVerdict
);
441 addAuthority(result
, "Insufficient Context");
446 // Result-creation helpers
448 void PolicyEngine::addAuthority(CFMutableDictionaryRef parent
, const char *label
, SQLite::int64 row
, CFTypeRef cacheInfo
)
450 CFRef
<CFMutableDictionaryRef
> auth
= makeCFMutableDictionary();
451 if (label
&& label
[0])
452 cfadd(auth
, "{%O=%s}", kSecAssessmentAssessmentSource
, label
);
454 CFDictionaryAddValue(auth
, kSecAssessmentAssessmentAuthorityRow
, CFTempNumber(row
));
455 if (overrideAssessment())
456 CFDictionaryAddValue(auth
, kSecAssessmentAssessmentAuthorityOverride
, kDisabledOverride
);
458 CFDictionaryAddValue(auth
, kSecAssessmentAssessmentFromCache
, cacheInfo
);
459 CFDictionaryAddValue(parent
, kSecAssessmentAssessmentAuthority
, auth
);
462 void PolicyEngine::addToAuthority(CFMutableDictionaryRef parent
, CFStringRef key
, CFTypeRef value
)
464 CFMutableDictionaryRef authority
= CFMutableDictionaryRef(CFDictionaryGetValue(parent
, kSecAssessmentAssessmentAuthority
));
466 CFDictionaryAddValue(authority
, key
, value
);
471 // Add a rule to the policy database
473 CFDictionaryRef
PolicyEngine::add(CFTypeRef inTarget
, AuthorityType type
, SecAssessmentFlags flags
, CFDictionaryRef context
)
475 // default type to execution
476 if (type
== kAuthorityInvalid
)
477 type
= kAuthorityExecute
;
479 authorizeUpdate(flags
, context
);
480 CFDictionary
ctx(context
, errSecCSInvalidAttributeValues
);
481 CFCopyRef
<CFTypeRef
> target
= inTarget
;
482 CFRef
<CFDataRef
> bookmark
= NULL
;
485 case kAuthorityExecute
:
486 normalizeTarget(target
, ctx
, true);
487 // bookmarks are untrusted and just a hint to callers
488 bookmark
= ctx
.get
<CFDataRef
>(kSecAssessmentRuleKeyBookmark
);
490 case kAuthorityInstall
:
491 if (inTarget
&& CFGetTypeID(inTarget
) == CFURLGetTypeID()) {
492 // no good way to turn an installer file into a requirement. Pretend to succeeed so caller proceeds
493 return cfmake
<CFDictionaryRef
>("{%O=%O}", kSecAssessmentAssessmentAuthorityOverride
, CFSTR("virtual install"));
496 case kAuthorityOpenDoc
:
497 // handle document-open differently: use quarantine flags for whitelisting
498 if (!target
|| CFGetTypeID(target
) != CFURLGetTypeID()) // can only "add" file paths
499 MacOSError::throwMe(errSecCSInvalidObjectRef
);
501 std::string spath
= cfString(target
.as
<CFURLRef
>());
502 FileQuarantine
qtn(spath
.c_str());
503 qtn
.setFlag(QTN_FLAG_ASSESSMENT_OK
);
504 qtn
.applyTo(spath
.c_str());
505 } catch (const CommonError
&error
) {
506 // could not set quarantine flag - report qualified success
507 return cfmake
<CFDictionaryRef
>("{%O=%O,'assessment:error'=%d}",
508 kSecAssessmentAssessmentAuthorityOverride
, CFSTR("error setting quarantine"), error
.osStatus());
510 return cfmake
<CFDictionaryRef
>("{%O=%O}", kSecAssessmentAssessmentAuthorityOverride
, CFSTR("unable to set quarantine"));
515 // if we now have anything else, we're busted
516 if (!target
|| CFGetTypeID(target
) != SecRequirementGetTypeID())
517 MacOSError::throwMe(errSecCSInvalidObjectRef
);
522 double expires
= never
;
525 if (CFNumberRef pri
= ctx
.get
<CFNumberRef
>(kSecAssessmentUpdateKeyPriority
))
526 CFNumberGetValue(pri
, kCFNumberDoubleType
, &priority
);
527 if (CFStringRef lab
= ctx
.get
<CFStringRef
>(kSecAssessmentUpdateKeyLabel
))
528 label
= cfString(lab
);
529 if (CFDateRef time
= ctx
.get
<CFDateRef
>(kSecAssessmentUpdateKeyExpires
))
530 // we're using Julian dates here; convert from CFDate
531 expires
= dateToJulian(time
);
532 if (CFBooleanRef allowing
= ctx
.get
<CFBooleanRef
>(kSecAssessmentUpdateKeyAllow
))
533 allow
= allowing
== kCFBooleanTrue
;
534 if (CFStringRef rem
= ctx
.get
<CFStringRef
>(kSecAssessmentUpdateKeyRemarks
))
535 remarks
= cfString(rem
);
537 CFRef
<CFStringRef
> requirementText
;
538 MacOSError::check(SecRequirementCopyString(target
.as
<SecRequirementRef
>(), kSecCSDefaultFlags
, &requirementText
.aref()));
539 SQLite::Transaction
xact(*this, SQLite3::Transaction::deferred
, "add_rule");
540 SQLite::Statement
insert(*this,
541 "INSERT INTO authority (type, allow, requirement, priority, label, expires, remarks)"
542 " VALUES (:type, :allow, :requirement, :priority, :label, :expires, :remarks);");
543 insert
.bind(":type").integer(type
);
544 insert
.bind(":allow").integer(allow
);
545 insert
.bind(":requirement") = requirementText
.get();
546 insert
.bind(":priority") = priority
;
548 insert
.bind(":label") = label
;
549 insert
.bind(":expires") = expires
;
550 if (!remarks
.empty())
551 insert
.bind(":remarks") = remarks
;
553 SQLite::int64 newRow
= this->lastInsert();
555 SQLite::Statement
bi(*this, "INSERT INTO bookmarkhints (bookmark, authority) VALUES (:bookmark, :authority)");
556 bi
.bind(":bookmark") = CFDataRef(bookmark
);
557 bi
.bind(":authority").integer(newRow
);
560 this->purgeObjects(priority
);
562 notify_post(kNotifySecAssessmentUpdate
);
563 return cfmake
<CFDictionaryRef
>("{%O=%d}", kSecAssessmentUpdateKeyRow
, newRow
);
567 CFDictionaryRef
PolicyEngine::remove(CFTypeRef target
, AuthorityType type
, SecAssessmentFlags flags
, CFDictionaryRef context
)
569 if (type
== kAuthorityOpenDoc
) {
570 // handle document-open differently: use quarantine flags for whitelisting
571 authorizeUpdate(flags
, context
);
572 if (!target
|| CFGetTypeID(target
) != CFURLGetTypeID())
573 MacOSError::throwMe(errSecCSInvalidObjectRef
);
574 std::string spath
= cfString(CFURLRef(target
)).c_str();
575 FileQuarantine
qtn(spath
.c_str());
576 qtn
.clearFlag(QTN_FLAG_ASSESSMENT_OK
);
577 qtn
.applyTo(spath
.c_str());
580 return manipulateRules("DELETE FROM authority", target
, type
, flags
, context
);
583 CFDictionaryRef
PolicyEngine::enable(CFTypeRef target
, AuthorityType type
, SecAssessmentFlags flags
, CFDictionaryRef context
)
585 return manipulateRules("UPDATE authority SET disabled = 0", target
, type
, flags
, context
);
588 CFDictionaryRef
PolicyEngine::disable(CFTypeRef target
, AuthorityType type
, SecAssessmentFlags flags
, CFDictionaryRef context
)
590 return manipulateRules("UPDATE authority SET disabled = 1", target
, type
, flags
, context
);
593 CFDictionaryRef
PolicyEngine::find(CFTypeRef target
, AuthorityType type
, SecAssessmentFlags flags
, CFDictionaryRef context
)
595 SQLite::Statement
query(*this);
596 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",
597 "scan_authority", target
, type
, flags
, context
,
598 " ORDER BY priority DESC");
599 CFRef
<CFMutableArrayRef
> found
= makeCFMutableArray(0);
600 while (query
.nextRow()) {
601 SQLite::int64 id
= query
[0];
602 int type
= int(query
[1]);
603 const char *requirement
= query
[2];
604 int allow
= int(query
[3]);
605 const char *label
= query
[4];
606 double priority
= query
[5];
607 const char *remarks
= query
[6];
608 double expires
= query
[7];
609 int disabled
= int(query
[8]);
610 CFRef
<CFDataRef
> bookmark
= query
[9].data();
611 CFRef
<CFMutableDictionaryRef
> rule
= makeCFMutableDictionary(5,
612 kSecAssessmentRuleKeyID
, CFTempNumber(id
).get(),
613 kSecAssessmentRuleKeyType
, CFRef
<CFStringRef
>(typeNameFor(type
)).get(),
614 kSecAssessmentRuleKeyRequirement
, CFTempString(requirement
).get(),
615 kSecAssessmentRuleKeyAllow
, allow
? kCFBooleanTrue
: kCFBooleanFalse
,
616 kSecAssessmentRuleKeyPriority
, CFTempNumber(priority
).get()
619 CFDictionaryAddValue(rule
, kSecAssessmentRuleKeyLabel
, CFTempString(label
));
621 CFDictionaryAddValue(rule
, kSecAssessmentRuleKeyRemarks
, CFTempString(remarks
));
622 if (expires
!= never
)
623 CFDictionaryAddValue(rule
, kSecAssessmentRuleKeyExpires
, CFRef
<CFDateRef
>(julianToDate(expires
)));
625 CFDictionaryAddValue(rule
, kSecAssessmentRuleKeyDisabled
, CFTempNumber(disabled
));
627 CFDictionaryAddValue(rule
, kSecAssessmentRuleKeyBookmark
, bookmark
);
628 CFArrayAppendValue(found
, rule
);
630 if (CFArrayGetCount(found
) == 0)
631 MacOSError::throwMe(errSecCSNoMatches
);
632 return cfmake
<CFDictionaryRef
>("{%O=%O}", kSecAssessmentUpdateKeyFound
, found
.get());
636 CFDictionaryRef
PolicyEngine::update(CFTypeRef target
, SecAssessmentFlags flags
, CFDictionaryRef context
)
638 AuthorityType type
= typeFor(context
, kAuthorityInvalid
);
639 CFStringRef edit
= CFStringRef(CFDictionaryGetValue(context
, kSecAssessmentContextKeyUpdate
));
640 CFDictionaryRef result
;
641 if (CFEqual(edit
, kSecAssessmentUpdateOperationAdd
))
642 result
= this->add(target
, type
, flags
, context
);
643 else if (CFEqual(edit
, kSecAssessmentUpdateOperationRemove
))
644 result
= this->remove(target
, type
, flags
, context
);
645 else if (CFEqual(edit
, kSecAssessmentUpdateOperationEnable
))
646 result
= this->enable(target
, type
, flags
, context
);
647 else if (CFEqual(edit
, kSecAssessmentUpdateOperationDisable
))
648 result
= this->disable(target
, type
, flags
, context
);
649 else if (CFEqual(edit
, kSecAssessmentUpdateOperationFind
))
650 result
= this->find(target
, type
, flags
, context
);
652 MacOSError::throwMe(errSecCSInvalidAttributeValues
);
654 result
= makeCFDictionary(0); // success, no details
660 // Construct and prepare an SQL query on the authority table, operating on some set of existing authority records.
661 // In essence, this appends a suitable WHERE clause to the stanza passed and prepares it on the statement given.
663 void PolicyEngine::selectRules(SQLite::Statement
&action
, std::string phrase
, std::string table
,
664 CFTypeRef inTarget
, AuthorityType type
, SecAssessmentFlags flags
, CFDictionaryRef context
, std::string suffix
/* = "" */)
666 CFDictionary
ctx(context
, errSecCSInvalidAttributeValues
);
667 CFCopyRef
<CFTypeRef
> target
= inTarget
;
668 normalizeTarget(target
, ctx
);
671 if (CFStringRef lab
= ctx
.get
<CFStringRef
>(kSecAssessmentUpdateKeyLabel
))
672 label
= cfString(CFStringRef(lab
));
676 if (type
== kAuthorityInvalid
) {
677 action
.query(phrase
+ suffix
);
679 action
.query(phrase
+ " WHERE " + table
+ ".type = :type" + suffix
);
680 action
.bind(":type").integer(type
);
682 } else { // have label
683 if (type
== kAuthorityInvalid
) {
684 action
.query(phrase
+ " WHERE " + table
+ ".label = :label" + suffix
);
686 action
.query(phrase
+ " WHERE " + table
+ ".type = :type AND " + table
+ ".label = :label" + suffix
);
687 action
.bind(":type").integer(type
);
689 action
.bind(":label") = label
;
691 } else if (CFGetTypeID(target
) == CFNumberGetTypeID()) {
692 action
.query(phrase
+ " WHERE " + table
+ ".id = :id" + suffix
);
693 action
.bind(":id").integer(cfNumber
<uint64_t>(target
.as
<CFNumberRef
>()));
694 } else if (CFGetTypeID(target
) == SecRequirementGetTypeID()) {
695 if (type
== kAuthorityInvalid
)
696 type
= kAuthorityExecute
;
697 CFRef
<CFStringRef
> requirementText
;
698 MacOSError::check(SecRequirementCopyString(target
.as
<SecRequirementRef
>(), kSecCSDefaultFlags
, &requirementText
.aref()));
699 action
.query(phrase
+ " WHERE " + table
+ ".type = :type AND " + table
+ ".requirement = :requirement" + suffix
);
700 action
.bind(":type").integer(type
);
701 action
.bind(":requirement") = requirementText
.get();
703 MacOSError::throwMe(errSecCSInvalidObjectRef
);
708 // Execute an atomic change to existing records in the authority table.
710 CFDictionaryRef
PolicyEngine::manipulateRules(const std::string
&stanza
,
711 CFTypeRef inTarget
, AuthorityType type
, SecAssessmentFlags flags
, CFDictionaryRef context
)
713 SQLite::Transaction
xact(*this, SQLite3::Transaction::deferred
, "rule_change");
714 SQLite::Statement
action(*this);
715 authorizeUpdate(flags
, context
);
716 selectRules(action
, stanza
, "authority", inTarget
, type
, flags
, context
);
718 unsigned int changes
= this->changes(); // latch change count
719 // We MUST purge objects with priority <= MAX(priority of any changed rules);
720 // but for now we just get lazy and purge them ALL.
722 this->purgeObjects(1.0E100
);
724 notify_post(kNotifySecAssessmentUpdate
);
725 return cfmake
<CFDictionaryRef
>("{%O=%d}", kSecAssessmentUpdateKeyCount
, changes
);
727 // no change; return an error
728 MacOSError::throwMe(errSecCSNoMatches
);
733 // Fill in extra information about the originator of cryptographic credentials found - if any
735 void PolicyEngine::setOrigin(CFArrayRef chain
, CFMutableDictionaryRef result
)
738 if (CFArrayGetCount(chain
) > 0)
739 if (SecCertificateRef leaf
= SecCertificateRef(CFArrayGetValueAtIndex(chain
, 0)))
740 if (CFStringRef summary
= SecCertificateCopyLongDescription(NULL
, leaf
, NULL
)) {
741 CFDictionarySetValue(result
, kSecAssessmentAssessmentOriginator
, summary
);
748 // Take an assessment outcome and record it in the object cache
750 void PolicyEngine::recordOutcome(SecStaticCodeRef code
, bool allow
, AuthorityType type
, double expires
, SQLite::int64 authority
)
752 CFRef
<CFDictionaryRef
> info
;
753 MacOSError::check(SecCodeCopySigningInformation(code
, kSecCSDefaultFlags
, &info
.aref()));
754 CFDataRef cdHash
= CFDataRef(CFDictionaryGetValue(info
, kSecCodeInfoUnique
));
755 assert(cdHash
); // was signed
756 CFRef
<CFURLRef
> path
;
757 MacOSError::check(SecCodeCopyPath(code
, kSecCSDefaultFlags
, &path
.aref()));
759 SQLite::Transaction
xact(*this, SQLite3::Transaction::deferred
, "caching");
760 SQLite::Statement
insert(*this,
761 "INSERT OR REPLACE INTO object (type, allow, hash, expires, path, authority)"
762 " VALUES (:type, :allow, :hash, :expires, :path,"
763 " CASE :authority WHEN 0 THEN (SELECT id FROM authority WHERE label = 'No Matching Rule') ELSE :authority END"
765 insert
.bind(":type").integer(type
);
766 insert
.bind(":allow").integer(allow
);
767 insert
.bind(":hash") = cdHash
;
768 insert
.bind(":expires") = expires
;
769 insert
.bind(":path") = cfString(path
);
770 insert
.bind(":authority").integer(authority
);
777 // Perform update authorization processing.
778 // Throws an exception if authorization is denied.
780 static void authorizeUpdate(SecAssessmentFlags flags
, CFDictionaryRef context
)
782 AuthorizationRef authorization
= NULL
;
785 if (CFTypeRef authkey
= CFDictionaryGetValue(context
, kSecAssessmentUpdateKeyAuthorization
))
786 if (CFGetTypeID(authkey
) == CFDataGetTypeID()) {
787 CFDataRef authdata
= CFDataRef(authkey
);
788 MacOSError::check(AuthorizationCreateFromExternalForm((AuthorizationExternalForm
*)CFDataGetBytePtr(authdata
), &authorization
));
790 if (authorization
== NULL
)
791 MacOSError::check(AuthorizationCreate(NULL
, NULL
, kAuthorizationFlagDefaults
, &authorization
));
793 AuthorizationItem right
[] = {
794 { "com.apple.security.assessment.update", 0, NULL
, 0 }
796 AuthorizationRights rights
= { sizeof(right
) / sizeof(right
[0]), right
};
797 MacOSError::check(AuthorizationCopyRights(authorization
, &rights
, NULL
,
798 kAuthorizationFlagExtendRights
| kAuthorizationFlagInteractionAllowed
, NULL
));
800 MacOSError::check(AuthorizationFree(authorization
, kAuthorizationFlagDefaults
));
805 // Perform common argument normalizations for update operations
807 static void normalizeTarget(CFRef
<CFTypeRef
> &target
, CFDictionary
&context
, bool signUnsigned
)
809 // turn CFURLs into (designated) SecRequirements
810 if (target
&& CFGetTypeID(target
) == CFURLGetTypeID()) {
811 CFRef
<SecStaticCodeRef
> code
;
812 MacOSError::check(SecStaticCodeCreateWithPath(target
.as
<CFURLRef
>(), kSecCSDefaultFlags
, &code
.aref()));
813 switch (OSStatus rc
= SecCodeCopyDesignatedRequirement(code
, kSecCSDefaultFlags
, (SecRequirementRef
*)&target
.aref())) {
815 // use the *default* DR to avoid unreasonably wide DRs opening up Gatekeeper to attack
816 CFRef
<CFDictionaryRef
> info
;
817 MacOSError::check(SecCodeCopySigningInformation(code
, kSecCSRequirementInformation
, &info
.aref()));
818 target
= CFDictionaryGetValue(info
, kSecCodeInfoImplicitDesignatedRequirement
);
821 case errSecCSUnsigned
:
823 // Ad-hoc sign the code in the system database. This requires root privileges.
824 CFRef
<SecCodeSignerRef
> signer
;
825 CFTemp
<CFDictionaryRef
> arguments("{%O=#N, %O=#N}", kSecCodeSignerDetached
, kSecCodeSignerIdentity
);
826 MacOSError::check(SecCodeSignerCreate(arguments
, kSecCSDefaultFlags
, &signer
.aref()));
827 MacOSError::check(SecCodeSignerAddSignature(signer
, code
, kSecCSDefaultFlags
));
828 MacOSError::check(SecCodeCopyDesignatedRequirement(code
, kSecCSDefaultFlags
, (SecRequirementRef
*)&target
.aref()));
831 MacOSError::check(rc
);
832 case errSecCSSignatureFailed
:
833 // recover certain cases of broken signatures (well, try)
834 if (codeInvalidityExceptions(code
, NULL
)) {
835 // Ad-hoc sign the code in place (requiring a writable subject). This requires root privileges.
836 CFRef
<SecCodeSignerRef
> signer
;
837 CFTemp
<CFDictionaryRef
> arguments("{%O=#N}", kSecCodeSignerIdentity
);
838 MacOSError::check(SecCodeSignerCreate(arguments
, kSecCSDefaultFlags
, &signer
.aref()));
839 MacOSError::check(SecCodeSignerAddSignature(signer
, code
, kSecCSDefaultFlags
));
840 MacOSError::check(SecCodeCopyDesignatedRequirement(code
, kSecCSDefaultFlags
, (SecRequirementRef
*)&target
.aref()));
843 MacOSError::check(rc
);
845 MacOSError::check(rc
);
847 if (context
.get(kSecAssessmentUpdateKeyRemarks
) == NULL
) {
848 // no explicit remarks; add one with the path
849 CFRef
<CFURLRef
> path
;
850 MacOSError::check(SecCodeCopyPath(code
, kSecCSDefaultFlags
, &path
.aref()));
851 CFMutableDictionaryRef dict
= makeCFMutableDictionary(context
.get());
852 CFDictionaryAddValue(dict
, kSecAssessmentUpdateKeyRemarks
, CFTempString(cfString(path
)));
860 // Process special overrides for invalidly signed code.
861 // This is the (hopefully minimal) concessions we make to keep hurting our customers
862 // for our own prior mistakes...
864 static bool codeInvalidityExceptions(SecStaticCodeRef code
, CFMutableDictionaryRef result
)
866 if (OSAIsRecognizedExecutableURL
) {
867 CFRef
<CFDictionaryRef
> info
;
868 MacOSError::check(SecCodeCopySigningInformation(code
, kSecCSDefaultFlags
, &info
.aref()));
869 if (CFURLRef executable
= CFURLRef(CFDictionaryGetValue(info
, kSecCodeInfoMainExecutable
))) {
871 if (OSAIsRecognizedExecutableURL(executable
, &error
)) {
873 CFDictionaryAddValue(result
,
874 kSecAssessmentAssessmentAuthorityOverride
, CFSTR("ignoring known invalid applet signature"));
883 } // end namespace CodeSigning
884 } // end namespace Security