2 * Copyright (c) 2011 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 <security_utilities/cfmunge.h>
26 #include <Security/Security.h>
27 #include <Security/SecRequirementPriv.h>
28 #include <Security/SecPolicyPriv.h>
30 #include <CoreServices/CoreServicesPriv.h>
31 #undef check // Macro! Yech.
34 namespace CodeSigning
{
36 static const time_t NEGATIVE_HOLD
= 60; // seconds for negative cache entries
42 PolicyEngine::PolicyEngine()
43 : PolicyDatabase(NULL
, SQLITE_OPEN_READWRITE
| SQLITE_OPEN_CREATE
)
47 PolicyEngine::~PolicyEngine()
52 // Top-level evaluation driver
54 void PolicyEngine::evaluate(CFURLRef path
, AuthorityType type
, SecAssessmentFlags flags
, CFDictionaryRef context
, CFMutableDictionaryRef result
)
57 case kAuthorityExecute
:
58 evaluateCode(path
, flags
, context
, result
);
60 case kAuthorityInstall
:
61 evaluateInstall(path
, flags
, context
, result
);
63 case kAuthorityOpenDoc
:
64 evaluateDocOpen(path
, flags
, context
, result
);
67 MacOSError::throwMe(errSecCSInvalidAttributeValues
);
75 // Read from disk, evaluate properly, cache as indicated. The whole thing, so far.
77 void PolicyEngine::evaluateCode(CFURLRef path
, SecAssessmentFlags flags
, CFDictionaryRef context
, CFMutableDictionaryRef result
)
79 const AuthorityType type
= kAuthorityExecute
;
81 CFRef
<SecStaticCodeRef
> code
;
82 MacOSError::check(SecStaticCodeCreateWithPath(path
, kSecCSDefaultFlags
, &code
.aref()));
84 if (flags
& kSecAssessmentFlagRequestOrigin
) {
85 CFRef
<CFDictionaryRef
> info
;
86 MacOSError::check(SecCodeCopySigningInformation(code
, kSecCSSigningInformation
, &info
.aref()));
87 if (CFArrayRef chain
= CFArrayRef(CFDictionaryGetValue(info
, kSecCodeInfoCertificates
)))
88 setOrigin(chain
, result
);
91 SecCSFlags validationFlags
= kSecCSDefaultFlags
;
92 if (overrideAssessment()) // we'll force the verdict to 'pass' at the end, so don't sweat validating code
93 validationFlags
= kSecCSBasicValidateOnly
;
95 SQLite::Statement
query(*this,
96 "SELECT allow, requirement, inhibit_cache, expires, id, label FROM authority WHERE type = ?1 ORDER BY priority DESC;");
97 query
.bind(1).integer(type
);
98 while (query
.nextRow()) {
99 bool allow
= int(query
[0]);
100 const char *reqString
= query
[1];
101 bool inhibit_cache
= query
[2];
102 time_t expires
= SQLite::int64(query
[3]);
103 SQLite3::int64 id
= query
[4];
104 const char *label
= query
[5];
106 if (expires
&& expires
< time(NULL
)) // no longer active
109 CFRef
<SecRequirementRef
> requirement
;
110 MacOSError::check(SecRequirementCreateWithString(CFTempString(reqString
), kSecCSDefaultFlags
, &requirement
.aref()));
111 switch (OSStatus rc
= SecStaticCodeCheckValidity(code
, validationFlags
, requirement
)) {
112 case noErr
: // success
114 case errSecCSUnsigned
:
115 cfadd(result
, "{%O=#F}", kSecAssessmentAssessmentVerdict
);
116 addAuthority(result
, "no usable signature");
118 case errSecCSSignatureFailed
:
119 case errSecCSSignatureInvalid
:
120 case errSecCSSignatureUnsupported
:
121 case errSecCSSignatureNotVerifiable
:
122 cfadd(result
, "{%O=#F}", kSecAssessmentAssessmentVerdict
);
123 addAuthority(result
, "invalid signature");
125 case errSecCSReqFailed
: // requirement missed, but otherwise okay
127 default: // broken in some way; all tests will fail like this so bail out
128 MacOSError::throwMe(rc
);
130 if (!inhibit_cache
&& !(flags
& kSecAssessmentFlagNoCache
)) // cache inhibit
131 this->recordOutcome(code
, allow
, type
, expires
, id
, label
);
132 cfadd(result
, "{%O=%B}", kSecAssessmentAssessmentVerdict
, allow
);
133 addAuthority(result
, label
, id
);
137 // no applicable authority. Deny by default
138 if (!(flags
& kSecAssessmentFlagNoCache
))
139 this->recordOutcome(code
, false, type
, time(NULL
) + NEGATIVE_HOLD
, 0, NULL
);
140 cfadd(result
, "{%O=%B}", kSecAssessmentAssessmentVerdict
, false);
141 addAuthority(result
, NULL
);
146 // Installer archive.
147 // Certs passed from caller (untrusted), no policy engine yet, no caching (since untrusted).
148 // The current "policy" is to trust any proper signature.
150 void PolicyEngine::evaluateInstall(CFURLRef path
, SecAssessmentFlags flags
, CFDictionaryRef context
, CFMutableDictionaryRef result
)
152 const AuthorityType type
= kAuthorityInstall
;
154 Xar
xar(cfString(path
).c_str());
156 if (!xar
.isSigned()) {
158 cfadd(result
, "{%O=%B}", kSecAssessmentAssessmentVerdict
, false);
159 addAuthority(result
, "no usable signature");
162 if (CFRef
<CFArrayRef
> certs
= xar
.copyCertChain()) {
163 CFRef
<SecPolicyRef
> policy
= SecPolicyCreateBasicX509();
164 CFRef
<SecTrustRef
> trust
;
165 MacOSError::check(SecTrustCreateWithCertificates(certs
, policy
, &trust
.aref()));
166 // MacOSError::check(SecTrustSetAnchorCertificates(trust, cfEmptyArray())); // no anchors
167 MacOSError::check(SecTrustSetOptions(trust
, kSecTrustOptionImplicitAnchors
));
169 SecTrustResultType trustResult
;
170 MacOSError::check(SecTrustEvaluate(trust
, &trustResult
));
171 CFRef
<CFArrayRef
> chain
;
172 CSSM_TP_APPLE_EVIDENCE_INFO
*info
;
173 MacOSError::check(SecTrustGetResult(trust
, &trustResult
, &chain
.aref(), &info
));
175 if (flags
& kSecAssessmentFlagRequestOrigin
)
176 setOrigin(chain
, result
);
178 switch (trustResult
) {
179 case kSecTrustResultProceed
:
180 case kSecTrustResultConfirm
:
181 case kSecTrustResultUnspecified
:
186 MacOSError::check(SecTrustGetCssmResultCode(trust
, &rc
));
187 MacOSError::throwMe(rc
);
191 SQLite::Statement
query(*this,
192 "SELECT allow, requirement, inhibit_cache, expires, id, label FROM authority WHERE type = ?1 ORDER BY priority DESC;");
193 query
.bind(1).integer(type
);
194 while (query
.nextRow()) {
195 bool allow
= int(query
[0]);
196 const char *reqString
= query
[1];
197 bool inhibit_cache
= query
[2];
198 time_t expires
= SQLite::int64(query
[3]);
199 SQLite3::int64 id
= query
[4];
200 const char *label
= query
[5];
202 if (expires
&& expires
< time(NULL
)) // no longer active
205 CFRef
<SecRequirementRef
> requirement
;
206 MacOSError::check(SecRequirementCreateWithString(CFTempString(reqString
), kSecCSDefaultFlags
, &requirement
.aref()));
207 switch (OSStatus rc
= SecRequirementEvaluate(requirement
, chain
, NULL
, kSecCSDefaultFlags
)) {
208 case noErr
: // success
210 case errSecCSReqFailed
: // requirement missed, but otherwise okay
212 default: // broken in some way; all tests will fail like this so bail out
213 MacOSError::throwMe(rc
);
215 // not adding to the object cache - we could, but it's not likely to be worth it
216 cfadd(result
, "{%O=%B}", kSecAssessmentAssessmentVerdict
, allow
);
217 addAuthority(result
, label
, id
);
223 // no applicable authority. Deny by default
224 cfadd(result
, "{%O=#F}", kSecAssessmentAssessmentVerdict
);
225 addAuthority(result
, NULL
);
230 // LaunchServices-layer document open.
231 // We don't cache those at present. If we ever do, we need to authenticate CoreServicesUIAgent as the source of its risk assessment.
233 void PolicyEngine::evaluateDocOpen(CFURLRef path
, SecAssessmentFlags flags
, CFDictionaryRef context
, CFMutableDictionaryRef result
)
236 if (CFStringRef riskCategory
= CFStringRef(CFDictionaryGetValue(context
, kLSDownloadRiskCategoryKey
))) {
237 if (CFEqual(riskCategory
, kLSRiskCategorySafe
)
238 || CFEqual(riskCategory
, kLSRiskCategoryNeutral
)
239 || CFEqual(riskCategory
, kLSRiskCategoryUnknown
)
240 || CFEqual(riskCategory
, kLSRiskCategoryMayContainUnsafeExecutable
)) {
241 cfadd(result
, "{%O=#T}", kSecAssessmentAssessmentVerdict
);
243 cfadd(result
, "{%O=#F}", kSecAssessmentAssessmentVerdict
);
245 addAuthority(result
, "_XProtect");
246 addToAuthority(result
, kLSDownloadRiskCategoryKey
, riskCategory
);
250 // insufficient information from LS - deny by default
251 cfadd(result
, "{%O=%F}", kSecAssessmentAssessmentVerdict
);
252 addAuthority(result
, NULL
);
256 void PolicyEngine::addAuthority(CFMutableDictionaryRef parent
, const char *label
, SQLite::int64 row
, CFTypeRef cacheInfo
)
258 CFRef
<CFMutableDictionaryRef
> auth
= makeCFMutableDictionary();
260 cfadd(auth
, "{%O=%s}", kSecAssessmentAssessmentSource
, label
);
262 CFDictionaryAddValue(auth
, kSecAssessmentAssessmentAuthorityRow
, CFTempNumber(row
));
264 CFDictionaryAddValue(auth
, kSecAssessmentAssessmentFromCache
, cacheInfo
);
265 CFDictionaryAddValue(parent
, kSecAssessmentAssessmentAuthority
, auth
);
268 void PolicyEngine::addToAuthority(CFMutableDictionaryRef parent
, CFStringRef key
, CFTypeRef value
)
270 CFMutableDictionaryRef authority
= CFMutableDictionaryRef(CFDictionaryGetValue(parent
, kSecAssessmentAssessmentAuthority
));
272 CFDictionaryAddValue(authority
, key
, value
);
277 // Add a rule to the policy database
279 bool PolicyEngine::add(CFURLRef path
, AuthorityType type
, SecAssessmentFlags flags
, CFDictionaryRef context
)
281 if (type
!= kAuthorityExecute
)
282 MacOSError::throwMe(errSecCSUnimplemented
);
287 if (CFTypeRef pri
= CFDictionaryGetValue(context
, kSecAssessmentUpdateKeyPriority
)) {
288 if (CFGetTypeID(pri
) != CFNumberGetTypeID())
289 MacOSError::throwMe(errSecCSBadDictionaryFormat
);
290 CFNumberGetValue(CFNumberRef(pri
), kCFNumberDoubleType
, &priority
);
292 if (CFTypeRef lab
= CFDictionaryGetValue(context
, kSecAssessmentUpdateKeyLabel
)) {
293 if (CFGetTypeID(lab
) != CFStringGetTypeID())
294 MacOSError::throwMe(errSecCSBadDictionaryFormat
);
295 label
= cfString(CFStringRef(lab
));
299 CFRef
<SecStaticCodeRef
> code
;
300 MacOSError::check(SecStaticCodeCreateWithPath(path
, kSecCSDefaultFlags
, &code
.aref()));
301 CFRef
<CFDictionaryRef
> info
;
302 MacOSError::check(SecCodeCopySigningInformation(code
, kSecCSDefaultFlags
, &info
.aref()));
303 CFRef
<SecRequirementRef
> dr
;
304 MacOSError::check(SecCodeCopyDesignatedRequirement(code
, kSecCSDefaultFlags
, &dr
.aref()));
306 CFRef
<CFStringRef
> requirementText
;
307 MacOSError::check(SecRequirementCopyString(dr
, kSecCSDefaultFlags
, &requirementText
.aref()));
308 SQLite::Statement
insert(*this,
309 "INSERT INTO authority (type, allow, requirement, priority, label) VALUES (?1, ?2, ?3, ?4, ?5);");
310 insert
.bind(1).integer(type
);
311 insert
.bind(2).integer(true);
312 insert
.bind(3) = requirementText
.get();
313 insert
.bind(4) = priority
;
314 insert
.bind(5) = label
.c_str();
321 // Fill in extra information about the originator of cryptographic credentials found - if any
323 void PolicyEngine::setOrigin(CFArrayRef chain
, CFMutableDictionaryRef result
)
326 if (CFArrayGetCount(chain
) > 0)
327 if (SecCertificateRef leaf
= SecCertificateRef(CFArrayGetValueAtIndex(chain
, 0)))
328 if (CFStringRef summary
= SecCertificateCopyLongDescription(NULL
, leaf
, NULL
))
329 CFDictionarySetValue(result
, kSecAssessmentAssessmentOriginator
, summary
);
334 // Take an assessment outcome and record it in the object cache
336 void PolicyEngine::recordOutcome(SecStaticCodeRef code
, bool allow
, AuthorityType type
, time_t expires
, int authority
, const char *label
)
338 CFRef
<CFDictionaryRef
> info
;
339 MacOSError::check(SecCodeCopySigningInformation(code
, kSecCSDefaultFlags
, &info
.aref()));
340 CFDataRef cdHash
= CFDataRef(CFDictionaryGetValue(info
, kSecCodeInfoUnique
));
341 CFRef
<CFURLRef
> path
;
342 MacOSError::check(SecCodeCopyPath(code
, kSecCSDefaultFlags
, &path
.aref()));
343 //@@@ should really be OR REPLACE IF EXPIRED... does it matter? @@@
344 SQLite::Transaction
xact(*this, SQLite3::Transaction::deferred
, "caching");
345 SQLite::Statement
insert(*this,
346 "INSERT OR REPLACE INTO object (type, allow, hash, expires, authority, label, path) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)");
347 insert
.bind(1).integer(type
);
348 insert
.bind(2).integer(allow
);
349 insert
.bind(3) = cdHash
;
351 insert
.bind(4).integer(expires
);
352 insert
.bind(5).integer(authority
);
354 insert
.bind(6) = label
;
355 insert
.bind(7) = cfString(path
).c_str();
361 } // end namespace CodeSigning
362 } // end namespace Security