]>
Commit | Line | Data |
---|---|---|
b1ab9ed8 A |
1 | /* |
2 | * Copyright (c) 2011-2012 Apple Inc. All Rights Reserved. | |
3 | * | |
4 | * @APPLE_LICENSE_HEADER_START@ | |
5 | * | |
6 | * This file contains Original Code and/or Modifications of Original Code | |
7 | * as defined in and that are subject to the Apple Public Source License | |
8 | * Version 2.0 (the 'License'). You may not use this file except in | |
9 | * compliance with the License. Please obtain a copy of the License at | |
10 | * http://www.opensource.apple.com/apsl/ and read it before using this | |
11 | * file. | |
12 | * | |
13 | * The Original Code and all software distributed under the License are | |
14 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER | |
15 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, | |
16 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, | |
17 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. | |
18 | * Please see the License for the specific language governing rights and | |
19 | * limitations under the License. | |
20 | * | |
21 | * @APPLE_LICENSE_HEADER_END@ | |
22 | */ | |
23 | #include "policyengine.h" | |
24 | #include "xar++.h" | |
25 | #include "quarantine++.h" | |
26 | #include "codesigning_dtrace.h" | |
27 | #include <security_utilities/cfmunge.h> | |
28 | #include <Security/Security.h> | |
29 | #include <Security/SecCodePriv.h> | |
30 | #include <Security/SecRequirementPriv.h> | |
31 | #include <Security/SecPolicyPriv.h> | |
32 | #include <Security/SecTrustPriv.h> | |
33 | #include <Security/SecCodeSigner.h> | |
34 | #include <Security/cssmapplePriv.h> | |
35 | #include <security_utilities/unix++.h> | |
36 | #include <notify.h> | |
37 | ||
38 | #include <CoreServices/CoreServicesPriv.h> | |
39 | #include "SecCodePriv.h" | |
40 | #undef check // Macro! Yech. | |
41 | ||
42 | extern "C" { | |
43 | #include <OpenScriptingUtilPriv.h> | |
44 | } | |
45 | ||
46 | ||
47 | namespace Security { | |
48 | namespace CodeSigning { | |
49 | ||
50 | static const double NEGATIVE_HOLD = 60.0/86400; // 60 seconds to cache negative outcomes | |
51 | ||
52 | static const char RECORDER_DIR[] = "/tmp/gke-"; // recorder mode destination for detached signatures | |
53 | enum { | |
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 | |
57 | }; | |
58 | ||
59 | ||
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; | |
64 | ||
65 | ||
66 | // | |
67 | // Core structure | |
68 | // | |
69 | PolicyEngine::PolicyEngine() | |
70 | : PolicyDatabase(NULL, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE) | |
71 | { | |
72 | } | |
73 | ||
74 | PolicyEngine::~PolicyEngine() | |
75 | { } | |
76 | ||
77 | ||
78 | // | |
79 | // Top-level evaluation driver | |
80 | // | |
81 | void PolicyEngine::evaluate(CFURLRef path, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result) | |
82 | { | |
83 | switch (type) { | |
84 | case kAuthorityExecute: | |
85 | evaluateCode(path, kAuthorityExecute, flags, context, result); | |
86 | break; | |
87 | case kAuthorityInstall: | |
88 | evaluateInstall(path, flags, context, result); | |
89 | break; | |
90 | case kAuthorityOpenDoc: | |
91 | evaluateDocOpen(path, flags, context, result); | |
92 | break; | |
93 | default: | |
94 | MacOSError::throwMe(errSecCSInvalidAttributeValues); | |
95 | break; | |
96 | } | |
97 | } | |
98 | ||
99 | ||
100 | // | |
101 | // Executable code. | |
102 | // Read from disk, evaluate properly, cache as indicated. The whole thing, so far. | |
103 | // | |
104 | void PolicyEngine::evaluateCode(CFURLRef path, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result) | |
105 | { | |
106 | FileQuarantine qtn(cfString(path).c_str()); | |
107 | if (qtn.flag(QTN_FLAG_HARD)) | |
108 | MacOSError::throwMe(errSecCSFileHardQuarantined); | |
109 | ||
110 | CFRef<SecStaticCodeRef> code; | |
111 | MacOSError::check(SecStaticCodeCreateWithPath(path, kSecCSDefaultFlags, &code.aref())); | |
112 | ||
113 | const SecCSFlags validationFlags = kSecCSEnforceRevocationChecks; | |
114 | ||
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]; | |
130 | ||
131 | CFRef<SecRequirementRef> requirement; | |
132 | MacOSError::check(SecRequirementCreateWithString(CFTempString(reqString), kSecCSDefaultFlags, &requirement.aref())); | |
133 | OSStatus rc = SecStaticCodeCheckValidity(code, validationFlags, requirement); | |
134 | ||
135 | if (rc == errSecCSUnsigned) { | |
136 | try { | |
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)); | |
144 | ||
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"; | |
151 | try { | |
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()); | |
156 | } catch (...) { } | |
157 | } | |
158 | ||
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); | |
165 | } | |
166 | ||
167 | // rerun the validation to update state | |
168 | rc = SecStaticCodeCheckValidity(code, validationFlags | kSecCSBasicValidateOnly, requirement); | |
169 | } catch (...) { } | |
170 | } | |
171 | ||
172 | switch (rc) { | |
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); | |
180 | } | |
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"); | |
187 | return; | |
188 | case errSecCSReqFailed: // requirement missed, but otherwise okay | |
189 | continue; | |
190 | default: // broken in some way; all tests will fail like this so bail out | |
191 | MacOSError::throwMe(rc); | |
192 | } | |
193 | if (disabled) { | |
194 | if (latentID == 0) { | |
195 | latentID = id; | |
196 | if (label) | |
197 | latentLabel = label; | |
198 | } | |
199 | continue; // the loop | |
200 | } | |
201 | ||
202 | CFRef<CFDictionaryRef> info; // as needed | |
203 | if (flags & kSecAssessmentFlagRequestOrigin) { | |
204 | if (!info) | |
205 | MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref())); | |
206 | if (CFArrayRef chain = CFArrayRef(CFDictionaryGetValue(info, kSecCodeInfoCertificates))) | |
207 | setOrigin(chain, result); | |
208 | } | |
209 | if (!(ruleFlags & kAuthorityFlagInhibitCache) && !(flags & kSecAssessmentFlagNoCache)) { // cache inhibit | |
210 | if (!info) | |
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); | |
217 | } | |
218 | } | |
219 | } | |
220 | if (allow) { | |
221 | if (SYSPOLICY_ASSESS_OUTCOME_ACCEPT_ENABLED()) { | |
222 | if (!info) | |
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); | |
226 | } | |
227 | } else { | |
228 | if (SYSPOLICY_ASSESS_OUTCOME_DENY_ENABLED() || SYSPOLICY_RECORDER_MODE_ENABLED()) { | |
229 | if (!info) | |
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); | |
236 | } | |
237 | } | |
238 | cfadd(result, "{%O=%B}", kSecAssessmentAssessmentVerdict, allow); | |
239 | addAuthority(result, label, id); | |
240 | return; | |
241 | } | |
242 | ||
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); | |
249 | } | |
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); | |
256 | } | |
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); | |
261 | } | |
262 | ||
263 | ||
264 | // | |
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. | |
268 | // | |
269 | void PolicyEngine::evaluateInstall(CFURLRef path, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result) | |
270 | { | |
271 | const AuthorityType type = kAuthorityInstall; | |
272 | ||
273 | Xar xar(cfString(path).c_str()); | |
274 | if (!xar) { | |
275 | // follow the code signing path | |
276 | evaluateCode(path, type, flags, context, result); | |
277 | return; | |
278 | } | |
279 | ||
280 | SQLite3::int64 latentID = 0; // first (highest priority) disabled matching ID | |
281 | std::string latentLabel; // ... and associated label, if any | |
282 | if (!xar.isSigned()) { | |
283 | // unsigned xar | |
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"); | |
288 | return; | |
289 | } | |
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)); | |
296 | ||
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)); | |
302 | ||
303 | if (flags & kSecAssessmentFlagRequestOrigin) | |
304 | setOrigin(chain, result); | |
305 | ||
306 | switch (trustResult) { | |
307 | case kSecTrustResultProceed: | |
308 | case kSecTrustResultUnspecified: | |
309 | break; | |
310 | default: | |
311 | { | |
312 | OSStatus rc; | |
313 | MacOSError::check(SecTrustGetCssmResultCode(trust, &rc)); | |
314 | MacOSError::throwMe(rc); | |
315 | } | |
316 | } | |
317 | ||
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]; | |
330 | ||
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 | |
335 | break; | |
336 | case errSecCSReqFailed: // requirement missed, but otherwise okay | |
337 | continue; | |
338 | default: // broken in some way; all tests will fail like this so bail out | |
339 | MacOSError::throwMe(rc); | |
340 | } | |
341 | if (disabled) { | |
342 | if (latentID == 0) { | |
343 | latentID = id; | |
344 | if (label) | |
345 | latentLabel = label; | |
346 | } | |
347 | continue; // the loop | |
348 | } | |
349 | ||
350 | if (SYSPOLICY_ASSESS_OUTCOME_ACCEPT_ENABLED() || SYSPOLICY_ASSESS_OUTCOME_DENY_ENABLED()) { | |
351 | if (allow) | |
352 | SYSPOLICY_ASSESS_OUTCOME_ACCEPT(cfString(path).c_str(), type, label, NULL); | |
353 | else | |
354 | SYSPOLICY_ASSESS_OUTCOME_DENY(cfString(path).c_str(), type, label, NULL); | |
355 | } | |
356 | ||
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); | |
360 | return; | |
361 | } | |
362 | } | |
363 | if (SYSPOLICY_ASSESS_OUTCOME_DEFAULT_ENABLED()) | |
364 | SYSPOLICY_ASSESS_OUTCOME_DEFAULT(cfString(path).c_str(), type, latentLabel.c_str(), NULL); | |
365 | ||
366 | // no applicable authority. Deny by default | |
367 | cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict); | |
368 | addAuthority(result, latentLabel.c_str(), latentID); | |
369 | } | |
370 | ||
371 | ||
372 | // | |
373 | // Create a suitable policy array for verification of installer signatures. | |
374 | // | |
375 | static SecPolicyRef makeCRLPolicy() | |
376 | { | |
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(); | |
386 | } | |
387 | ||
388 | static SecPolicyRef makeOCSPPolicy() | |
389 | { | |
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(); | |
399 | } | |
400 | ||
401 | static CFTypeRef installerPolicy() | |
402 | { | |
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()); | |
407 | } | |
408 | ||
409 | ||
410 | // | |
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. | |
413 | // | |
414 | void PolicyEngine::evaluateDocOpen(CFURLRef path, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result) | |
415 | { | |
416 | if (context) { | |
417 | if (CFStringRef riskCategory = CFStringRef(CFDictionaryGetValue(context, kLSDownloadRiskCategoryKey))) { | |
418 | FileQuarantine qtn(cfString(path).c_str()); | |
419 | ||
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"); | |
431 | } else { | |
432 | cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict); | |
433 | addAuthority(result, "_XProtect"); | |
434 | } | |
435 | addToAuthority(result, kLSDownloadRiskCategoryKey, riskCategory); | |
436 | return; | |
437 | } | |
438 | } | |
439 | // insufficient information from LS - deny by default | |
440 | cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict); | |
441 | addAuthority(result, "Insufficient Context"); | |
442 | } | |
443 | ||
444 | ||
445 | // | |
446 | // Result-creation helpers | |
447 | // | |
448 | void PolicyEngine::addAuthority(CFMutableDictionaryRef parent, const char *label, SQLite::int64 row, CFTypeRef cacheInfo) | |
449 | { | |
450 | CFRef<CFMutableDictionaryRef> auth = makeCFMutableDictionary(); | |
451 | if (label && label[0]) | |
452 | cfadd(auth, "{%O=%s}", kSecAssessmentAssessmentSource, label); | |
453 | if (row) | |
454 | CFDictionaryAddValue(auth, kSecAssessmentAssessmentAuthorityRow, CFTempNumber(row)); | |
455 | if (overrideAssessment()) | |
456 | CFDictionaryAddValue(auth, kSecAssessmentAssessmentAuthorityOverride, kDisabledOverride); | |
457 | if (cacheInfo) | |
458 | CFDictionaryAddValue(auth, kSecAssessmentAssessmentFromCache, cacheInfo); | |
459 | CFDictionaryAddValue(parent, kSecAssessmentAssessmentAuthority, auth); | |
460 | } | |
461 | ||
462 | void PolicyEngine::addToAuthority(CFMutableDictionaryRef parent, CFStringRef key, CFTypeRef value) | |
463 | { | |
464 | CFMutableDictionaryRef authority = CFMutableDictionaryRef(CFDictionaryGetValue(parent, kSecAssessmentAssessmentAuthority)); | |
465 | assert(authority); | |
466 | CFDictionaryAddValue(authority, key, value); | |
467 | } | |
468 | ||
469 | ||
470 | // | |
471 | // Add a rule to the policy database | |
472 | // | |
473 | CFDictionaryRef PolicyEngine::add(CFTypeRef inTarget, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context) | |
474 | { | |
475 | // default type to execution | |
476 | if (type == kAuthorityInvalid) | |
477 | type = kAuthorityExecute; | |
478 | ||
479 | authorizeUpdate(flags, context); | |
480 | CFDictionary ctx(context, errSecCSInvalidAttributeValues); | |
481 | CFCopyRef<CFTypeRef> target = inTarget; | |
482 | CFRef<CFDataRef> bookmark = NULL; | |
483 | ||
484 | switch (type) { | |
485 | case kAuthorityExecute: | |
486 | normalizeTarget(target, ctx, true); | |
487 | // bookmarks are untrusted and just a hint to callers | |
488 | bookmark = ctx.get<CFDataRef>(kSecAssessmentRuleKeyBookmark); | |
489 | break; | |
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")); | |
494 | } | |
495 | break; | |
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); | |
500 | try { | |
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()); | |
509 | } catch (...) { | |
510 | return cfmake<CFDictionaryRef>("{%O=%O}", kSecAssessmentAssessmentAuthorityOverride, CFSTR("unable to set quarantine")); | |
511 | } | |
512 | return NULL; | |
513 | } | |
514 | ||
515 | // if we now have anything else, we're busted | |
516 | if (!target || CFGetTypeID(target) != SecRequirementGetTypeID()) | |
517 | MacOSError::throwMe(errSecCSInvalidObjectRef); | |
518 | ||
519 | double priority = 0; | |
520 | string label; | |
521 | bool allow = true; | |
522 | double expires = never; | |
523 | string remarks; | |
524 | ||
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); | |
536 | ||
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; | |
547 | if (!label.empty()) | |
548 | insert.bind(":label") = label; | |
549 | insert.bind(":expires") = expires; | |
550 | if (!remarks.empty()) | |
551 | insert.bind(":remarks") = remarks; | |
552 | insert.execute(); | |
553 | SQLite::int64 newRow = this->lastInsert(); | |
554 | if (bookmark) { | |
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); | |
558 | bi.execute(); | |
559 | } | |
560 | this->purgeObjects(priority); | |
561 | xact.commit(); | |
562 | notify_post(kNotifySecAssessmentUpdate); | |
563 | return cfmake<CFDictionaryRef>("{%O=%d}", kSecAssessmentUpdateKeyRow, newRow); | |
564 | } | |
565 | ||
566 | ||
567 | CFDictionaryRef PolicyEngine::remove(CFTypeRef target, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context) | |
568 | { | |
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()); | |
578 | return NULL; | |
579 | } | |
580 | return manipulateRules("DELETE FROM authority", target, type, flags, context); | |
581 | } | |
582 | ||
583 | CFDictionaryRef PolicyEngine::enable(CFTypeRef target, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context) | |
584 | { | |
585 | return manipulateRules("UPDATE authority SET disabled = 0", target, type, flags, context); | |
586 | } | |
587 | ||
588 | CFDictionaryRef PolicyEngine::disable(CFTypeRef target, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context) | |
589 | { | |
590 | return manipulateRules("UPDATE authority SET disabled = 1", target, type, flags, context); | |
591 | } | |
592 | ||
593 | CFDictionaryRef PolicyEngine::find(CFTypeRef target, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context) | |
594 | { | |
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() | |
617 | ); | |
618 | if (label) | |
619 | CFDictionaryAddValue(rule, kSecAssessmentRuleKeyLabel, CFTempString(label)); | |
620 | if (remarks) | |
621 | CFDictionaryAddValue(rule, kSecAssessmentRuleKeyRemarks, CFTempString(remarks)); | |
622 | if (expires != never) | |
623 | CFDictionaryAddValue(rule, kSecAssessmentRuleKeyExpires, CFRef<CFDateRef>(julianToDate(expires))); | |
624 | if (disabled) | |
625 | CFDictionaryAddValue(rule, kSecAssessmentRuleKeyDisabled, CFTempNumber(disabled)); | |
626 | if (bookmark) | |
627 | CFDictionaryAddValue(rule, kSecAssessmentRuleKeyBookmark, bookmark); | |
628 | CFArrayAppendValue(found, rule); | |
629 | } | |
630 | if (CFArrayGetCount(found) == 0) | |
631 | MacOSError::throwMe(errSecCSNoMatches); | |
632 | return cfmake<CFDictionaryRef>("{%O=%O}", kSecAssessmentUpdateKeyFound, found.get()); | |
633 | } | |
634 | ||
635 | ||
636 | CFDictionaryRef PolicyEngine::update(CFTypeRef target, SecAssessmentFlags flags, CFDictionaryRef context) | |
637 | { | |
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); | |
651 | else | |
652 | MacOSError::throwMe(errSecCSInvalidAttributeValues); | |
653 | if (result == NULL) | |
654 | result = makeCFDictionary(0); // success, no details | |
655 | return result; | |
656 | } | |
657 | ||
658 | ||
659 | // | |
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. | |
662 | // | |
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 /* = "" */) | |
665 | { | |
666 | CFDictionary ctx(context, errSecCSInvalidAttributeValues); | |
667 | CFCopyRef<CFTypeRef> target = inTarget; | |
668 | normalizeTarget(target, ctx); | |
669 | ||
670 | string label; | |
671 | if (CFStringRef lab = ctx.get<CFStringRef>(kSecAssessmentUpdateKeyLabel)) | |
672 | label = cfString(CFStringRef(lab)); | |
673 | ||
674 | if (!target) { | |
675 | if (label.empty()) { | |
676 | if (type == kAuthorityInvalid) { | |
677 | action.query(phrase + suffix); | |
678 | } else { | |
679 | action.query(phrase + " WHERE " + table + ".type = :type" + suffix); | |
680 | action.bind(":type").integer(type); | |
681 | } | |
682 | } else { // have label | |
683 | if (type == kAuthorityInvalid) { | |
684 | action.query(phrase + " WHERE " + table + ".label = :label" + suffix); | |
685 | } else { | |
686 | action.query(phrase + " WHERE " + table + ".type = :type AND " + table + ".label = :label" + suffix); | |
687 | action.bind(":type").integer(type); | |
688 | } | |
689 | action.bind(":label") = label; | |
690 | } | |
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(); | |
702 | } else | |
703 | MacOSError::throwMe(errSecCSInvalidObjectRef); | |
704 | } | |
705 | ||
706 | ||
707 | // | |
708 | // Execute an atomic change to existing records in the authority table. | |
709 | // | |
710 | CFDictionaryRef PolicyEngine::manipulateRules(const std::string &stanza, | |
711 | CFTypeRef inTarget, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context) | |
712 | { | |
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); | |
717 | action.execute(); | |
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. | |
721 | if (changes) { | |
722 | this->purgeObjects(1.0E100); | |
723 | xact.commit(); | |
724 | notify_post(kNotifySecAssessmentUpdate); | |
725 | return cfmake<CFDictionaryRef>("{%O=%d}", kSecAssessmentUpdateKeyCount, changes); | |
726 | } | |
727 | // no change; return an error | |
728 | MacOSError::throwMe(errSecCSNoMatches); | |
729 | } | |
730 | ||
731 | ||
732 | // | |
733 | // Fill in extra information about the originator of cryptographic credentials found - if any | |
734 | // | |
735 | void PolicyEngine::setOrigin(CFArrayRef chain, CFMutableDictionaryRef result) | |
736 | { | |
737 | if (chain) | |
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); | |
742 | CFRelease(summary); | |
743 | } | |
744 | } | |
745 | ||
746 | ||
747 | // | |
748 | // Take an assessment outcome and record it in the object cache | |
749 | // | |
750 | void PolicyEngine::recordOutcome(SecStaticCodeRef code, bool allow, AuthorityType type, double expires, SQLite::int64 authority) | |
751 | { | |
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())); | |
758 | assert(expires); | |
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" | |
764 | " );"); | |
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); | |
771 | insert.execute(); | |
772 | xact.commit(); | |
773 | } | |
774 | ||
775 | ||
776 | // | |
777 | // Perform update authorization processing. | |
778 | // Throws an exception if authorization is denied. | |
779 | // | |
780 | static void authorizeUpdate(SecAssessmentFlags flags, CFDictionaryRef context) | |
781 | { | |
782 | AuthorizationRef authorization = NULL; | |
783 | ||
784 | if (context) | |
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)); | |
789 | } | |
790 | if (authorization == NULL) | |
791 | MacOSError::check(AuthorizationCreate(NULL, NULL, kAuthorizationFlagDefaults, &authorization)); | |
792 | ||
793 | AuthorizationItem right[] = { | |
794 | { "com.apple.security.assessment.update", 0, NULL, 0 } | |
795 | }; | |
796 | AuthorizationRights rights = { sizeof(right) / sizeof(right[0]), right }; | |
797 | MacOSError::check(AuthorizationCopyRights(authorization, &rights, NULL, | |
798 | kAuthorizationFlagExtendRights | kAuthorizationFlagInteractionAllowed, NULL)); | |
799 | ||
800 | MacOSError::check(AuthorizationFree(authorization, kAuthorizationFlagDefaults)); | |
801 | } | |
802 | ||
803 | ||
804 | // | |
805 | // Perform common argument normalizations for update operations | |
806 | // | |
807 | static void normalizeTarget(CFRef<CFTypeRef> &target, CFDictionary &context, bool signUnsigned) | |
808 | { | |
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())) { | |
814 | case noErr: { | |
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); | |
819 | } | |
820 | break; | |
821 | case errSecCSUnsigned: | |
822 | if (signUnsigned) { | |
823 | // Ad-hoc sign the code temporarily so we can get its code requirement | |
824 | CFRef<CFDataRef> signature = CFDataCreateMutable(NULL, 0); | |
825 | CFRef<SecCodeSignerRef> signer; | |
826 | CFTemp<CFDictionaryRef> arguments("{%O=%O, %O=#N}", kSecCodeSignerDetached, signature.get(), kSecCodeSignerIdentity); | |
827 | MacOSError::check(SecCodeSignerCreate(arguments, kSecCSDefaultFlags, &signer.aref())); | |
828 | MacOSError::check(SecCodeSignerAddSignature(signer, code, kSecCSDefaultFlags)); | |
829 | MacOSError::check(SecCodeSetDetachedSignature(code, signature, kSecCSDefaultFlags)); | |
830 | MacOSError::check(SecCodeCopyDesignatedRequirement(code, kSecCSDefaultFlags, (SecRequirementRef *)&target.aref())); | |
831 | break; | |
832 | } | |
833 | MacOSError::check(rc); | |
834 | case errSecCSSignatureFailed: | |
835 | // recover certain cases of broken signatures (well, try) | |
836 | if (codeInvalidityExceptions(code, NULL)) { | |
837 | // Ad-hoc sign the code in place (requiring a writable subject). This requires root privileges. | |
838 | CFRef<SecCodeSignerRef> signer; | |
839 | CFTemp<CFDictionaryRef> arguments("{%O=#N}", kSecCodeSignerIdentity); | |
840 | MacOSError::check(SecCodeSignerCreate(arguments, kSecCSDefaultFlags, &signer.aref())); | |
841 | MacOSError::check(SecCodeSignerAddSignature(signer, code, kSecCSDefaultFlags)); | |
842 | MacOSError::check(SecCodeCopyDesignatedRequirement(code, kSecCSDefaultFlags, (SecRequirementRef *)&target.aref())); | |
843 | break; | |
844 | } | |
845 | MacOSError::check(rc); | |
846 | default: | |
847 | MacOSError::check(rc); | |
848 | } | |
849 | if (context.get(kSecAssessmentUpdateKeyRemarks) == NULL) { | |
850 | // no explicit remarks; add one with the path | |
851 | CFRef<CFURLRef> path; | |
852 | MacOSError::check(SecCodeCopyPath(code, kSecCSDefaultFlags, &path.aref())); | |
853 | CFMutableDictionaryRef dict = makeCFMutableDictionary(context.get()); | |
854 | CFDictionaryAddValue(dict, kSecAssessmentUpdateKeyRemarks, CFTempString(cfString(path))); | |
855 | context.take(dict); | |
856 | } | |
857 | } | |
858 | } | |
859 | ||
860 | ||
861 | // | |
862 | // Process special overrides for invalidly signed code. | |
863 | // This is the (hopefully minimal) concessions we make to keep hurting our customers | |
864 | // for our own prior mistakes... | |
865 | // | |
866 | static bool codeInvalidityExceptions(SecStaticCodeRef code, CFMutableDictionaryRef result) | |
867 | { | |
868 | if (OSAIsRecognizedExecutableURL) { | |
869 | CFRef<CFDictionaryRef> info; | |
870 | MacOSError::check(SecCodeCopySigningInformation(code, kSecCSDefaultFlags, &info.aref())); | |
871 | if (CFURLRef executable = CFURLRef(CFDictionaryGetValue(info, kSecCodeInfoMainExecutable))) { | |
872 | SInt32 error; | |
873 | if (OSAIsRecognizedExecutableURL(executable, &error)) { | |
874 | if (result) | |
875 | CFDictionaryAddValue(result, | |
876 | kSecAssessmentAssessmentAuthorityOverride, CFSTR("ignoring known invalid applet signature")); | |
877 | return true; | |
878 | } | |
879 | } | |
880 | } | |
881 | return false; | |
882 | } | |
883 | ||
884 | ||
885 | } // end namespace CodeSigning | |
886 | } // end namespace Security |