]> git.saurik.com Git - apple/security.git/blob - libsecurity_codesigning/lib/policyengine.cpp
bbd91e0939e51ae9f26c8990047952707d74f8e5
[apple/security.git] / libsecurity_codesigning / lib / policyengine.cpp
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 "diskrep.h"
39 #include "codedirectory.h"
40 #include "csutilities.h"
41 #include "StaticCode.h"
42
43 #include <CoreServices/CoreServicesPriv.h>
44 #include "SecCodePriv.h"
45 #undef check // Macro! Yech.
46
47 extern "C" {
48 #include <OpenScriptingUtilPriv.h>
49 }
50
51
52 namespace Security {
53 namespace CodeSigning {
54
55 static const double NEGATIVE_HOLD = 60.0/86400; // 60 seconds to cache negative outcomes
56
57 static const char RECORDER_DIR[] = "/tmp/gke-"; // recorder mode destination for detached signatures
58 enum {
59 recorder_code_untrusted = 0, // signed but untrusted
60 recorder_code_adhoc = 1, // unsigned; signature recorded
61 recorder_code_unable = 2, // unsigned; unable to record signature
62 };
63
64
65 static void authorizeUpdate(SecAssessmentFlags flags, CFDictionaryRef context);
66 static void normalizeTarget(CFRef<CFTypeRef> &target, CFDictionary &context, std::string *signUnsigned = NULL);
67 static bool codeInvalidityExceptions(SecStaticCodeRef code, CFMutableDictionaryRef result);
68 static CFTypeRef installerPolicy() CF_RETURNS_RETAINED;
69
70
71 //
72 // Core structure
73 //
74 PolicyEngine::PolicyEngine()
75 : PolicyDatabase(NULL, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE)
76 {
77 }
78
79 PolicyEngine::~PolicyEngine()
80 { }
81
82
83 //
84 // Top-level evaluation driver
85 //
86 void PolicyEngine::evaluate(CFURLRef path, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result)
87 {
88 // update GKE
89 installExplicitSet(gkeAuthFile, gkeSigsFile);
90
91 switch (type) {
92 case kAuthorityExecute:
93 evaluateCode(path, kAuthorityExecute, flags, context, result);
94 break;
95 case kAuthorityInstall:
96 evaluateInstall(path, flags, context, result);
97 break;
98 case kAuthorityOpenDoc:
99 evaluateDocOpen(path, flags, context, result);
100 break;
101 default:
102 MacOSError::throwMe(errSecCSInvalidAttributeValues);
103 break;
104 }
105 }
106
107
108 //
109 // Whitelist pre-screen processing.
110 // Whitelist-matching unsigned code is expensive since we have to generate a full code signature
111 // just to see if we match a whitelist authority entry. This class generates a light(er)-weight
112 // prescreen and matches it against a hint in an authority record generated from the (detached recorded)
113 // code signature at time of whitelist recording.
114 // This is just a heuristic to cheaply rule out guaranteed mismatches. When in doubt, we go ahead
115 // and do the full work.
116 //
117 class WhitelistPrescreen {
118 public:
119 WhitelistPrescreen(SecStaticCodeRef code)
120 : mRep(SecStaticCode::requiredStatic(code)->diskRep()) { }
121
122 bool reject(const char *screen, const char *remarks);
123
124 private:
125 std::string create(char type, SHA1 &hash);
126
127 RefPointer<DiskRep> mRep; // DiskRep representing the code
128 std::string mScreen; // calculated screen (on demand)
129 };
130
131 bool WhitelistPrescreen::reject(const char *screen, const char *remarks)
132 {
133 if (!screen) { // authority record has no screen to match - apply heuristic
134 if (remarks && mRep->mainExecutablePath() != remarks) // not an allow record (or moved)
135 return true;
136 else
137 return false; // can't rule out; proceed
138 }
139
140 if (mScreen.empty()) {
141 if (CFRef<CFDataRef> info = mRep->component(cdInfoSlot)) {
142 SHA1 hash;
143 hash.update(CFDataGetBytePtr(info), CFDataGetLength(info));
144 mScreen = create('I', hash);
145 } else if (mRep->mainExecutableImage()) {
146 mScreen = "N";
147 } else {
148 SHA1 hash;
149 hashFileData(mRep->mainExecutablePath().c_str(), &hash);
150 mScreen = create('M', hash);
151 }
152 }
153
154 return screen != mScreen;
155 }
156
157 std::string WhitelistPrescreen::create(char type, SHA1 &hash)
158 {
159 SHA1::Digest digest;
160 hash.finish(digest);
161 char buffer[2*SHA1::digestLength + 2] = { type };
162 for (size_t n = 0; n < SHA1::digestLength; n++)
163 sprintf(buffer + 1 + 2*n, "%02.2x", digest[n]);
164 return buffer;
165 }
166
167
168 //
169 // Executable code.
170 // Read from disk, evaluate properly, cache as indicated. The whole thing, so far.
171 //
172 void PolicyEngine::evaluateCode(CFURLRef path, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result,
173 bool handleUnsignedCode /* = true */)
174 {
175 FileQuarantine qtn(cfString(path).c_str());
176 if (qtn.flag(QTN_FLAG_HARD))
177 MacOSError::throwMe(errSecCSFileHardQuarantined);
178
179 CFRef<SecStaticCodeRef> code;
180 MacOSError::check(SecStaticCodeCreateWithPath(path, kSecCSDefaultFlags, &code.aref()));
181 OSStatus rc = noErr; // last validation error
182
183 const SecCSFlags validationFlags = kSecCSEnforceRevocationChecks;
184
185 WhitelistPrescreen whitelistScreen(code); // pre-screening filter for whitelist pre-screening (only)
186
187 SQLite::Statement query(*this,
188 "SELECT allow, requirement, id, label, expires, flags, disabled, filter_unsigned, remarks FROM scan_authority"
189 " WHERE type = :type"
190 " ORDER BY priority DESC;");
191 query.bind(":type").integer(type);
192 SQLite3::int64 latentID = 0; // first (highest priority) disabled matching ID
193 std::string latentLabel; // ... and associated label, if any
194 while (query.nextRow()) {
195 bool allow = int(query[0]);
196 const char *reqString = query[1];
197 SQLite3::int64 id = query[2];
198 const char *label = query[3];
199 double expires = query[4];
200 sqlite3_int64 ruleFlags = query[5];
201 SQLite3::int64 disabled = query[6];
202 const char *filter = query[7];
203 const char *remarks = query[8];
204
205 CFRef<SecRequirementRef> requirement;
206 MacOSError::check(SecRequirementCreateWithString(CFTempString(reqString), kSecCSDefaultFlags, &requirement.aref()));
207 rc = SecStaticCodeCheckValidity(code, validationFlags, requirement);
208
209 // ad-hoc sign unsigned code, skip of Gatekeeper is off or the rule is disabled; but always do it for whitelist recording
210 if (rc == errSecCSUnsigned && handleUnsignedCode && (!(disabled || overrideAssessment()) || SYSPOLICY_RECORDER_MODE_ENABLED())) {
211 if (!SYSPOLICY_RECORDER_MODE_ENABLED()) {
212 // apply whitelist pre-screening to speed things up for non-matches
213 if (ruleFlags & kAuthorityFlagDefault) // can't ever match standard rules with unsigned code
214 continue;
215 if (whitelistScreen.reject(filter, remarks)) // apply whitelist pre-filter
216 continue;
217 }
218 try {
219 // ad-hoc sign the code and attach the signature
220 CFRef<CFDataRef> signature = CFDataCreateMutable(NULL, 0);
221 CFTemp<CFDictionaryRef> arguments("{%O=%O, %O=#N}", kSecCodeSignerDetached, signature.get(), kSecCodeSignerIdentity);
222 CFRef<SecCodeSignerRef> signer;
223 MacOSError::check(SecCodeSignerCreate(arguments, kSecCSDefaultFlags, &signer.aref()));
224 MacOSError::check(SecCodeSignerAddSignature(signer, code, kSecCSDefaultFlags));
225 MacOSError::check(SecCodeSetDetachedSignature(code, signature, kSecCSDefaultFlags));
226
227 // if we're in GKE recording mode, save that signature and report its location
228 if (SYSPOLICY_RECORDER_MODE_ENABLED()) {
229 int status = recorder_code_unable; // ephemeral signature (not recorded)
230 if (geteuid() == 0) {
231 CFRef<CFUUIDRef> uuid = CFUUIDCreate(NULL);
232 std::string sigfile = RECORDER_DIR + cfStringRelease(CFUUIDCreateString(NULL, uuid)) + ".tsig";
233 try {
234 UnixPlusPlus::AutoFileDesc fd(sigfile, O_WRONLY | O_CREAT);
235 fd.write(CFDataGetBytePtr(signature), CFDataGetLength(signature));
236 status = recorder_code_adhoc; // recorded signature
237 SYSPOLICY_RECORDER_MODE_ADHOC_PATH(cfString(path).c_str(), type, sigfile.c_str());
238 } catch (...) { }
239 }
240
241 // now report the D probe itself
242 CFRef<CFDictionaryRef> info;
243 MacOSError::check(SecCodeCopySigningInformation(code, kSecCSDefaultFlags, &info.aref()));
244 CFDataRef cdhash = CFDataRef(CFDictionaryGetValue(info, kSecCodeInfoUnique));
245 SYSPOLICY_RECORDER_MODE(cfString(path).c_str(), type, "",
246 cdhash ? CFDataGetBytePtr(cdhash) : NULL, status);
247 }
248
249 // rerun the validation to update state
250 rc = SecStaticCodeCheckValidity(code, validationFlags | kSecCSBasicValidateOnly, requirement);
251 } catch (...) { }
252 }
253
254 switch (rc) {
255 case noErr: // well signed and satisfies requirement...
256 break; // ... continue below
257 case errSecCSSignatureFailed:
258 if (!codeInvalidityExceptions(code, result)) {
259 if (SYSPOLICY_ASSESS_OUTCOME_BROKEN_ENABLED())
260 SYSPOLICY_ASSESS_OUTCOME_BROKEN(cfString(path).c_str(), type, false);
261 MacOSError::throwMe(rc);
262 }
263 if (SYSPOLICY_ASSESS_OUTCOME_BROKEN_ENABLED())
264 SYSPOLICY_ASSESS_OUTCOME_BROKEN(cfString(path).c_str(), type, true);
265 // treat as unsigned to fix problems in the field
266 case errSecCSUnsigned:
267 if (handleUnsignedCode) {
268 cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict);
269 addAuthority(result, "no usable signature");
270 }
271 return;
272 case errSecCSReqFailed: // requirement missed, but otherwise okay
273 continue;
274 default: // broken in some way; all tests will fail like this so bail out
275 MacOSError::throwMe(rc);
276 }
277 if (disabled) {
278 if (latentID == 0) {
279 latentID = id;
280 if (label)
281 latentLabel = label;
282 }
283 continue; // the loop
284 }
285
286 CFRef<CFDictionaryRef> info; // as needed
287 if (flags & kSecAssessmentFlagRequestOrigin) {
288 if (!info)
289 MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref()));
290 if (CFArrayRef chain = CFArrayRef(CFDictionaryGetValue(info, kSecCodeInfoCertificates)))
291 setOrigin(chain, result);
292 }
293 if (!(ruleFlags & kAuthorityFlagInhibitCache) && !(flags & kSecAssessmentFlagNoCache)) { // cache inhibit
294 if (!info)
295 MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref()));
296 if (SecTrustRef trust = SecTrustRef(CFDictionaryGetValue(info, kSecCodeInfoTrust))) {
297 CFRef<CFDictionaryRef> xinfo;
298 MacOSError::check(SecTrustCopyExtendedResult(trust, &xinfo.aref()));
299 if (CFDateRef limit = CFDateRef(CFDictionaryGetValue(xinfo, kSecTrustExpirationDate))) {
300 this->recordOutcome(code, allow, type, min(expires, dateToJulian(limit)), id);
301 }
302 }
303 }
304 if (allow) {
305 if (SYSPOLICY_ASSESS_OUTCOME_ACCEPT_ENABLED()) {
306 if (!info)
307 MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref()));
308 CFDataRef cdhash = CFDataRef(CFDictionaryGetValue(info, kSecCodeInfoUnique));
309 SYSPOLICY_ASSESS_OUTCOME_ACCEPT(cfString(path).c_str(), type, label, cdhash ? CFDataGetBytePtr(cdhash) : NULL);
310 }
311 } else {
312 if (SYSPOLICY_ASSESS_OUTCOME_DENY_ENABLED() || SYSPOLICY_RECORDER_MODE_ENABLED()) {
313 if (!info)
314 MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref()));
315 CFDataRef cdhash = CFDataRef(CFDictionaryGetValue(info, kSecCodeInfoUnique));
316 std::string cpath = cfString(path);
317 const void *hashp = cdhash ? CFDataGetBytePtr(cdhash) : NULL;
318 SYSPOLICY_ASSESS_OUTCOME_DENY(cpath.c_str(), type, label, hashp);
319 SYSPOLICY_RECORDER_MODE(cpath.c_str(), type, label, hashp, recorder_code_untrusted);
320 }
321 }
322 cfadd(result, "{%O=%B}", kSecAssessmentAssessmentVerdict, allow);
323 addAuthority(result, label, id);
324 return;
325 }
326
327 if (rc == errSecCSUnsigned) { // skipped all applicable rules due to pre-screening
328 cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict);
329 addAuthority(result, "no usable signature");
330 return;
331 }
332
333 // no applicable authority (but signed, perhaps temporarily). Deny by default
334 CFRef<CFDictionaryRef> info;
335 MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref()));
336 if (flags & kSecAssessmentFlagRequestOrigin) {
337 if (CFArrayRef chain = CFArrayRef(CFDictionaryGetValue(info, kSecCodeInfoCertificates)))
338 setOrigin(chain, result);
339 }
340 if (SYSPOLICY_ASSESS_OUTCOME_DEFAULT_ENABLED() || SYSPOLICY_RECORDER_MODE_ENABLED()) {
341 CFDataRef cdhash = CFDataRef(CFDictionaryGetValue(info, kSecCodeInfoUnique));
342 const void *hashp = cdhash ? CFDataGetBytePtr(cdhash) : NULL;
343 std::string cpath = cfString(path);
344 SYSPOLICY_ASSESS_OUTCOME_DEFAULT(cpath.c_str(), type, latentLabel.c_str(), hashp);
345 SYSPOLICY_RECORDER_MODE(cpath.c_str(), type, latentLabel.c_str(), hashp, 0);
346 }
347 if (!(flags & kSecAssessmentFlagNoCache))
348 this->recordOutcome(code, false, type, this->julianNow() + NEGATIVE_HOLD, latentID);
349 cfadd(result, "{%O=%B}", kSecAssessmentAssessmentVerdict, false);
350 addAuthority(result, latentLabel.c_str(), latentID);
351 }
352
353
354 //
355 // Installer archive.
356 // Hybrid policy: If we detect an installer signature, use and validate that.
357 // If we don't, check for a code signature instead.
358 //
359 void PolicyEngine::evaluateInstall(CFURLRef path, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result)
360 {
361 const AuthorityType type = kAuthorityInstall;
362
363 Xar xar(cfString(path).c_str());
364 if (!xar) {
365 // follow the code signing path
366 evaluateCode(path, type, flags, context, result);
367 return;
368 }
369
370 SQLite3::int64 latentID = 0; // first (highest priority) disabled matching ID
371 std::string latentLabel; // ... and associated label, if any
372 if (!xar.isSigned()) {
373 // unsigned xar
374 if (SYSPOLICY_ASSESS_OUTCOME_UNSIGNED_ENABLED())
375 SYSPOLICY_ASSESS_OUTCOME_UNSIGNED(cfString(path).c_str(), type);
376 cfadd(result, "{%O=%B}", kSecAssessmentAssessmentVerdict, false);
377 addAuthority(result, "no usable signature");
378 return;
379 }
380 if (CFRef<CFArrayRef> certs = xar.copyCertChain()) {
381 CFRef<CFTypeRef> policy = installerPolicy();
382 CFRef<SecTrustRef> trust;
383 MacOSError::check(SecTrustCreateWithCertificates(certs, policy, &trust.aref()));
384 // MacOSError::check(SecTrustSetAnchorCertificates(trust, cfEmptyArray())); // no anchors
385 MacOSError::check(SecTrustSetOptions(trust, kSecTrustOptionAllowExpired | kSecTrustOptionImplicitAnchors));
386
387 SecTrustResultType trustResult;
388 MacOSError::check(SecTrustEvaluate(trust, &trustResult));
389 CFRef<CFArrayRef> chain;
390 CSSM_TP_APPLE_EVIDENCE_INFO *info;
391 MacOSError::check(SecTrustGetResult(trust, &trustResult, &chain.aref(), &info));
392
393 if (flags & kSecAssessmentFlagRequestOrigin)
394 setOrigin(chain, result);
395
396 switch (trustResult) {
397 case kSecTrustResultProceed:
398 case kSecTrustResultUnspecified:
399 break;
400 default:
401 {
402 OSStatus rc;
403 MacOSError::check(SecTrustGetCssmResultCode(trust, &rc));
404 MacOSError::throwMe(rc);
405 }
406 }
407
408 SQLite::Statement query(*this,
409 "SELECT allow, requirement, id, label, flags, disabled FROM scan_authority"
410 " WHERE type = :type"
411 " ORDER BY priority DESC;");
412 query.bind(":type").integer(type);
413 while (query.nextRow()) {
414 bool allow = int(query[0]);
415 const char *reqString = query[1];
416 SQLite3::int64 id = query[2];
417 const char *label = query[3];
418 //sqlite_uint64 ruleFlags = query[4];
419 SQLite3::int64 disabled = query[5];
420
421 CFRef<SecRequirementRef> requirement;
422 MacOSError::check(SecRequirementCreateWithString(CFTempString(reqString), kSecCSDefaultFlags, &requirement.aref()));
423 switch (OSStatus rc = SecRequirementEvaluate(requirement, chain, NULL, kSecCSDefaultFlags)) {
424 case noErr: // success
425 break;
426 case errSecCSReqFailed: // requirement missed, but otherwise okay
427 continue;
428 default: // broken in some way; all tests will fail like this so bail out
429 MacOSError::throwMe(rc);
430 }
431 if (disabled) {
432 if (latentID == 0) {
433 latentID = id;
434 if (label)
435 latentLabel = label;
436 }
437 continue; // the loop
438 }
439
440 if (SYSPOLICY_ASSESS_OUTCOME_ACCEPT_ENABLED() || SYSPOLICY_ASSESS_OUTCOME_DENY_ENABLED()) {
441 if (allow)
442 SYSPOLICY_ASSESS_OUTCOME_ACCEPT(cfString(path).c_str(), type, label, NULL);
443 else
444 SYSPOLICY_ASSESS_OUTCOME_DENY(cfString(path).c_str(), type, label, NULL);
445 }
446
447 // not adding to the object cache - we could, but it's not likely to be worth it
448 cfadd(result, "{%O=%B}", kSecAssessmentAssessmentVerdict, allow);
449 addAuthority(result, label, id);
450 return;
451 }
452 }
453 if (SYSPOLICY_ASSESS_OUTCOME_DEFAULT_ENABLED())
454 SYSPOLICY_ASSESS_OUTCOME_DEFAULT(cfString(path).c_str(), type, latentLabel.c_str(), NULL);
455
456 // no applicable authority. Deny by default
457 cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict);
458 addAuthority(result, latentLabel.c_str(), latentID);
459 }
460
461
462 //
463 // Create a suitable policy array for verification of installer signatures.
464 //
465 static SecPolicyRef makeCRLPolicy()
466 {
467 CFRef<SecPolicyRef> policy;
468 MacOSError::check(SecPolicyCopy(CSSM_CERT_X_509v3, &CSSMOID_APPLE_TP_REVOCATION_CRL, &policy.aref()));
469 CSSM_APPLE_TP_CRL_OPTIONS options;
470 memset(&options, 0, sizeof(options));
471 options.Version = CSSM_APPLE_TP_CRL_OPTS_VERSION;
472 options.CrlFlags = CSSM_TP_ACTION_FETCH_CRL_FROM_NET | CSSM_TP_ACTION_CRL_SUFFICIENT;
473 CSSM_DATA optData = { sizeof(options), (uint8 *)&options };
474 MacOSError::check(SecPolicySetValue(policy, &optData));
475 return policy.yield();
476 }
477
478 static SecPolicyRef makeOCSPPolicy()
479 {
480 CFRef<SecPolicyRef> policy;
481 MacOSError::check(SecPolicyCopy(CSSM_CERT_X_509v3, &CSSMOID_APPLE_TP_REVOCATION_OCSP, &policy.aref()));
482 CSSM_APPLE_TP_OCSP_OPTIONS options;
483 memset(&options, 0, sizeof(options));
484 options.Version = CSSM_APPLE_TP_OCSP_OPTS_VERSION;
485 options.Flags = CSSM_TP_ACTION_OCSP_SUFFICIENT;
486 CSSM_DATA optData = { sizeof(options), (uint8 *)&options };
487 MacOSError::check(SecPolicySetValue(policy, &optData));
488 return policy.yield();
489 }
490
491 static CFTypeRef installerPolicy()
492 {
493 CFRef<SecPolicyRef> base = SecPolicyCreateBasicX509();
494 CFRef<SecPolicyRef> crl = makeCRLPolicy();
495 CFRef<SecPolicyRef> ocsp = makeOCSPPolicy();
496 return makeCFArray(3, base.get(), crl.get(), ocsp.get());
497 }
498
499
500 //
501 // LaunchServices-layer document open.
502 // We don't cache those at present. If we ever do, we need to authenticate CoreServicesUIAgent as the source of its risk assessment.
503 //
504 void PolicyEngine::evaluateDocOpen(CFURLRef path, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result)
505 {
506 if (context) {
507 if (CFStringRef riskCategory = CFStringRef(CFDictionaryGetValue(context, kLSDownloadRiskCategoryKey))) {
508 FileQuarantine qtn(cfString(path).c_str());
509
510 if (CFEqual(riskCategory, kLSRiskCategorySafe)
511 || CFEqual(riskCategory, kLSRiskCategoryNeutral)
512 || CFEqual(riskCategory, kLSRiskCategoryUnknown)
513 || CFEqual(riskCategory, kLSRiskCategoryMayContainUnsafeExecutable)) {
514 cfadd(result, "{%O=#T}", kSecAssessmentAssessmentVerdict);
515 addAuthority(result, "_XProtect");
516 } else if (qtn.flag(QTN_FLAG_HARD)) {
517 MacOSError::throwMe(errSecCSFileHardQuarantined);
518 } else if (qtn.flag(QTN_FLAG_ASSESSMENT_OK)) {
519 cfadd(result, "{%O=#T}", kSecAssessmentAssessmentVerdict);
520 addAuthority(result, "Prior Assessment");
521 } else if (!overrideAssessment()) { // no need to do more work if we're off
522 try {
523 evaluateCode(path, kAuthorityExecute, flags, context, result, false);
524 } catch (...) {
525 // some documents can't be code signed, so this may be quite benign
526 }
527 }
528 if (CFDictionaryGetValue(result, kSecAssessmentAssessmentVerdict) == NULL) { // no code signature to help us out
529 cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict);
530 addAuthority(result, "_XProtect");
531 }
532 addToAuthority(result, kLSDownloadRiskCategoryKey, riskCategory);
533 return;
534 }
535 }
536 // insufficient information from LS - deny by default
537 cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict);
538 addAuthority(result, "Insufficient Context");
539 }
540
541
542 //
543 // Result-creation helpers
544 //
545 void PolicyEngine::addAuthority(CFMutableDictionaryRef parent, const char *label, SQLite::int64 row, CFTypeRef cacheInfo)
546 {
547 CFRef<CFMutableDictionaryRef> auth = makeCFMutableDictionary();
548 if (label && label[0])
549 cfadd(auth, "{%O=%s}", kSecAssessmentAssessmentSource, label);
550 if (row)
551 CFDictionaryAddValue(auth, kSecAssessmentAssessmentAuthorityRow, CFTempNumber(row));
552 if (overrideAssessment())
553 CFDictionaryAddValue(auth, kSecAssessmentAssessmentAuthorityOverride, kDisabledOverride);
554 if (cacheInfo)
555 CFDictionaryAddValue(auth, kSecAssessmentAssessmentFromCache, cacheInfo);
556 CFDictionaryAddValue(parent, kSecAssessmentAssessmentAuthority, auth);
557 }
558
559 void PolicyEngine::addToAuthority(CFMutableDictionaryRef parent, CFStringRef key, CFTypeRef value)
560 {
561 CFMutableDictionaryRef authority = CFMutableDictionaryRef(CFDictionaryGetValue(parent, kSecAssessmentAssessmentAuthority));
562 assert(authority);
563 CFDictionaryAddValue(authority, key, value);
564 }
565
566
567 //
568 // Add a rule to the policy database
569 //
570 CFDictionaryRef PolicyEngine::add(CFTypeRef inTarget, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context)
571 {
572 // default type to execution
573 if (type == kAuthorityInvalid)
574 type = kAuthorityExecute;
575
576 authorizeUpdate(flags, context);
577 CFDictionary ctx(context, errSecCSInvalidAttributeValues);
578 CFCopyRef<CFTypeRef> target = inTarget;
579 CFRef<CFDataRef> bookmark = NULL;
580 std::string filter_unsigned;
581
582 switch (type) {
583 case kAuthorityExecute:
584 normalizeTarget(target, ctx, &filter_unsigned);
585 // bookmarks are untrusted and just a hint to callers
586 bookmark = ctx.get<CFDataRef>(kSecAssessmentRuleKeyBookmark);
587 break;
588 case kAuthorityInstall:
589 if (inTarget && CFGetTypeID(inTarget) == CFURLGetTypeID()) {
590 // no good way to turn an installer file into a requirement. Pretend to succeeed so caller proceeds
591 return cfmake<CFDictionaryRef>("{%O=%O}", kSecAssessmentAssessmentAuthorityOverride, CFSTR("virtual install"));
592 }
593 break;
594 case kAuthorityOpenDoc:
595 // handle document-open differently: use quarantine flags for whitelisting
596 if (!target || CFGetTypeID(target) != CFURLGetTypeID()) // can only "add" file paths
597 MacOSError::throwMe(errSecCSInvalidObjectRef);
598 try {
599 std::string spath = cfString(target.as<CFURLRef>());
600 FileQuarantine qtn(spath.c_str());
601 qtn.setFlag(QTN_FLAG_ASSESSMENT_OK);
602 qtn.applyTo(spath.c_str());
603 } catch (const CommonError &error) {
604 // could not set quarantine flag - report qualified success
605 return cfmake<CFDictionaryRef>("{%O=%O,'assessment:error'=%d}",
606 kSecAssessmentAssessmentAuthorityOverride, CFSTR("error setting quarantine"), error.osStatus());
607 } catch (...) {
608 return cfmake<CFDictionaryRef>("{%O=%O}", kSecAssessmentAssessmentAuthorityOverride, CFSTR("unable to set quarantine"));
609 }
610 return NULL;
611 }
612
613 // if we now have anything else, we're busted
614 if (!target || CFGetTypeID(target) != SecRequirementGetTypeID())
615 MacOSError::throwMe(errSecCSInvalidObjectRef);
616
617 double priority = 0;
618 string label;
619 bool allow = true;
620 double expires = never;
621 string remarks;
622
623 if (CFNumberRef pri = ctx.get<CFNumberRef>(kSecAssessmentUpdateKeyPriority))
624 CFNumberGetValue(pri, kCFNumberDoubleType, &priority);
625 if (CFStringRef lab = ctx.get<CFStringRef>(kSecAssessmentUpdateKeyLabel))
626 label = cfString(lab);
627 if (CFDateRef time = ctx.get<CFDateRef>(kSecAssessmentUpdateKeyExpires))
628 // we're using Julian dates here; convert from CFDate
629 expires = dateToJulian(time);
630 if (CFBooleanRef allowing = ctx.get<CFBooleanRef>(kSecAssessmentUpdateKeyAllow))
631 allow = allowing == kCFBooleanTrue;
632 if (CFStringRef rem = ctx.get<CFStringRef>(kSecAssessmentUpdateKeyRemarks))
633 remarks = cfString(rem);
634
635 CFRef<CFStringRef> requirementText;
636 MacOSError::check(SecRequirementCopyString(target.as<SecRequirementRef>(), kSecCSDefaultFlags, &requirementText.aref()));
637 SQLite::Transaction xact(*this, SQLite3::Transaction::deferred, "add_rule");
638 SQLite::Statement insert(*this,
639 "INSERT INTO authority (type, allow, requirement, priority, label, expires, filter_unsigned, remarks)"
640 " VALUES (:type, :allow, :requirement, :priority, :label, :expires, :filter_unsigned, :remarks);");
641 insert.bind(":type").integer(type);
642 insert.bind(":allow").integer(allow);
643 insert.bind(":requirement") = requirementText.get();
644 insert.bind(":priority") = priority;
645 if (!label.empty())
646 insert.bind(":label") = label;
647 insert.bind(":expires") = expires;
648 insert.bind(":filter_unsigned") = filter_unsigned.empty() ? NULL : filter_unsigned.c_str();
649 if (!remarks.empty())
650 insert.bind(":remarks") = remarks;
651 insert.execute();
652 SQLite::int64 newRow = this->lastInsert();
653 if (bookmark) {
654 SQLite::Statement bi(*this, "INSERT INTO bookmarkhints (bookmark, authority) VALUES (:bookmark, :authority)");
655 bi.bind(":bookmark") = CFDataRef(bookmark);
656 bi.bind(":authority").integer(newRow);
657 bi.execute();
658 }
659 this->purgeObjects(priority);
660 xact.commit();
661 notify_post(kNotifySecAssessmentUpdate);
662 return cfmake<CFDictionaryRef>("{%O=%d}", kSecAssessmentUpdateKeyRow, newRow);
663 }
664
665
666 CFDictionaryRef PolicyEngine::remove(CFTypeRef target, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context)
667 {
668 if (type == kAuthorityOpenDoc) {
669 // handle document-open differently: use quarantine flags for whitelisting
670 authorizeUpdate(flags, context);
671 if (!target || CFGetTypeID(target) != CFURLGetTypeID())
672 MacOSError::throwMe(errSecCSInvalidObjectRef);
673 std::string spath = cfString(CFURLRef(target)).c_str();
674 FileQuarantine qtn(spath.c_str());
675 qtn.clearFlag(QTN_FLAG_ASSESSMENT_OK);
676 qtn.applyTo(spath.c_str());
677 return NULL;
678 }
679 return manipulateRules("DELETE FROM authority", target, type, flags, context);
680 }
681
682 CFDictionaryRef PolicyEngine::enable(CFTypeRef target, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context)
683 {
684 return manipulateRules("UPDATE authority SET disabled = 0", target, type, flags, context);
685 }
686
687 CFDictionaryRef PolicyEngine::disable(CFTypeRef target, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context)
688 {
689 return manipulateRules("UPDATE authority SET disabled = 1", target, type, flags, context);
690 }
691
692 CFDictionaryRef PolicyEngine::find(CFTypeRef target, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context)
693 {
694 SQLite::Statement query(*this);
695 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",
696 "scan_authority", target, type, flags, context,
697 " ORDER BY priority DESC");
698 CFRef<CFMutableArrayRef> found = makeCFMutableArray(0);
699 while (query.nextRow()) {
700 SQLite::int64 id = query[0];
701 int type = int(query[1]);
702 const char *requirement = query[2];
703 int allow = int(query[3]);
704 const char *label = query[4];
705 double priority = query[5];
706 const char *remarks = query[6];
707 double expires = query[7];
708 int disabled = int(query[8]);
709 CFRef<CFDataRef> bookmark = query[9].data();
710 CFRef<CFMutableDictionaryRef> rule = makeCFMutableDictionary(5,
711 kSecAssessmentRuleKeyID, CFTempNumber(id).get(),
712 kSecAssessmentRuleKeyType, CFRef<CFStringRef>(typeNameFor(type)).get(),
713 kSecAssessmentRuleKeyRequirement, CFTempString(requirement).get(),
714 kSecAssessmentRuleKeyAllow, allow ? kCFBooleanTrue : kCFBooleanFalse,
715 kSecAssessmentRuleKeyPriority, CFTempNumber(priority).get()
716 );
717 if (label)
718 CFDictionaryAddValue(rule, kSecAssessmentRuleKeyLabel, CFTempString(label));
719 if (remarks)
720 CFDictionaryAddValue(rule, kSecAssessmentRuleKeyRemarks, CFTempString(remarks));
721 if (expires != never)
722 CFDictionaryAddValue(rule, kSecAssessmentRuleKeyExpires, CFRef<CFDateRef>(julianToDate(expires)));
723 if (disabled)
724 CFDictionaryAddValue(rule, kSecAssessmentRuleKeyDisabled, CFTempNumber(disabled));
725 if (bookmark)
726 CFDictionaryAddValue(rule, kSecAssessmentRuleKeyBookmark, bookmark);
727 CFArrayAppendValue(found, rule);
728 }
729 if (CFArrayGetCount(found) == 0)
730 MacOSError::throwMe(errSecCSNoMatches);
731 return cfmake<CFDictionaryRef>("{%O=%O}", kSecAssessmentUpdateKeyFound, found.get());
732 }
733
734
735 CFDictionaryRef PolicyEngine::update(CFTypeRef target, SecAssessmentFlags flags, CFDictionaryRef context)
736 {
737 // update GKE
738 installExplicitSet(gkeAuthFile, gkeSigsFile);
739
740 AuthorityType type = typeFor(context, kAuthorityInvalid);
741 CFStringRef edit = CFStringRef(CFDictionaryGetValue(context, kSecAssessmentContextKeyUpdate));
742 CFDictionaryRef result;
743 if (CFEqual(edit, kSecAssessmentUpdateOperationAdd))
744 result = this->add(target, type, flags, context);
745 else if (CFEqual(edit, kSecAssessmentUpdateOperationRemove))
746 result = this->remove(target, type, flags, context);
747 else if (CFEqual(edit, kSecAssessmentUpdateOperationEnable))
748 result = this->enable(target, type, flags, context);
749 else if (CFEqual(edit, kSecAssessmentUpdateOperationDisable))
750 result = this->disable(target, type, flags, context);
751 else if (CFEqual(edit, kSecAssessmentUpdateOperationFind))
752 result = this->find(target, type, flags, context);
753 else
754 MacOSError::throwMe(errSecCSInvalidAttributeValues);
755 if (result == NULL)
756 result = makeCFDictionary(0); // success, no details
757 return result;
758 }
759
760
761 //
762 // Construct and prepare an SQL query on the authority table, operating on some set of existing authority records.
763 // In essence, this appends a suitable WHERE clause to the stanza passed and prepares it on the statement given.
764 //
765 void PolicyEngine::selectRules(SQLite::Statement &action, std::string phrase, std::string table,
766 CFTypeRef inTarget, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context, std::string suffix /* = "" */)
767 {
768 CFDictionary ctx(context, errSecCSInvalidAttributeValues);
769 CFCopyRef<CFTypeRef> target = inTarget;
770 std::string filter_unsigned; // ignored; used just to trigger ad-hoc signing
771 normalizeTarget(target, ctx, &filter_unsigned);
772
773 string label;
774 if (CFStringRef lab = ctx.get<CFStringRef>(kSecAssessmentUpdateKeyLabel))
775 label = cfString(CFStringRef(lab));
776
777 if (!target) {
778 if (label.empty()) {
779 if (type == kAuthorityInvalid) {
780 action.query(phrase + suffix);
781 } else {
782 action.query(phrase + " WHERE " + table + ".type = :type" + suffix);
783 action.bind(":type").integer(type);
784 }
785 } else { // have label
786 if (type == kAuthorityInvalid) {
787 action.query(phrase + " WHERE " + table + ".label = :label" + suffix);
788 } else {
789 action.query(phrase + " WHERE " + table + ".type = :type AND " + table + ".label = :label" + suffix);
790 action.bind(":type").integer(type);
791 }
792 action.bind(":label") = label;
793 }
794 } else if (CFGetTypeID(target) == CFNumberGetTypeID()) {
795 action.query(phrase + " WHERE " + table + ".id = :id" + suffix);
796 action.bind(":id").integer(cfNumber<uint64_t>(target.as<CFNumberRef>()));
797 } else if (CFGetTypeID(target) == SecRequirementGetTypeID()) {
798 if (type == kAuthorityInvalid)
799 type = kAuthorityExecute;
800 CFRef<CFStringRef> requirementText;
801 MacOSError::check(SecRequirementCopyString(target.as<SecRequirementRef>(), kSecCSDefaultFlags, &requirementText.aref()));
802 action.query(phrase + " WHERE " + table + ".type = :type AND " + table + ".requirement = :requirement" + suffix);
803 action.bind(":type").integer(type);
804 action.bind(":requirement") = requirementText.get();
805 } else
806 MacOSError::throwMe(errSecCSInvalidObjectRef);
807 }
808
809
810 //
811 // Execute an atomic change to existing records in the authority table.
812 //
813 CFDictionaryRef PolicyEngine::manipulateRules(const std::string &stanza,
814 CFTypeRef inTarget, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context)
815 {
816 SQLite::Transaction xact(*this, SQLite3::Transaction::deferred, "rule_change");
817 SQLite::Statement action(*this);
818 authorizeUpdate(flags, context);
819 selectRules(action, stanza, "authority", inTarget, type, flags, context);
820 action.execute();
821 unsigned int changes = this->changes(); // latch change count
822 // We MUST purge objects with priority <= MAX(priority of any changed rules);
823 // but for now we just get lazy and purge them ALL.
824 if (changes) {
825 this->purgeObjects(1.0E100);
826 xact.commit();
827 notify_post(kNotifySecAssessmentUpdate);
828 return cfmake<CFDictionaryRef>("{%O=%d}", kSecAssessmentUpdateKeyCount, changes);
829 }
830 // no change; return an error
831 MacOSError::throwMe(errSecCSNoMatches);
832 }
833
834
835 //
836 // Fill in extra information about the originator of cryptographic credentials found - if any
837 //
838 void PolicyEngine::setOrigin(CFArrayRef chain, CFMutableDictionaryRef result)
839 {
840 if (chain)
841 if (CFArrayGetCount(chain) > 0)
842 if (SecCertificateRef leaf = SecCertificateRef(CFArrayGetValueAtIndex(chain, 0)))
843 if (CFStringRef summary = SecCertificateCopyLongDescription(NULL, leaf, NULL)) {
844 CFDictionarySetValue(result, kSecAssessmentAssessmentOriginator, summary);
845 CFRelease(summary);
846 }
847 }
848
849
850 //
851 // Take an assessment outcome and record it in the object cache
852 //
853 void PolicyEngine::recordOutcome(SecStaticCodeRef code, bool allow, AuthorityType type, double expires, SQLite::int64 authority)
854 {
855 CFRef<CFDictionaryRef> info;
856 MacOSError::check(SecCodeCopySigningInformation(code, kSecCSDefaultFlags, &info.aref()));
857 CFDataRef cdHash = CFDataRef(CFDictionaryGetValue(info, kSecCodeInfoUnique));
858 assert(cdHash); // was signed
859 CFRef<CFURLRef> path;
860 MacOSError::check(SecCodeCopyPath(code, kSecCSDefaultFlags, &path.aref()));
861 assert(expires);
862 SQLite::Transaction xact(*this, SQLite3::Transaction::deferred, "caching");
863 SQLite::Statement insert(*this,
864 "INSERT OR REPLACE INTO object (type, allow, hash, expires, path, authority)"
865 " VALUES (:type, :allow, :hash, :expires, :path,"
866 " CASE :authority WHEN 0 THEN (SELECT id FROM authority WHERE label = 'No Matching Rule') ELSE :authority END"
867 " );");
868 insert.bind(":type").integer(type);
869 insert.bind(":allow").integer(allow);
870 insert.bind(":hash") = cdHash;
871 insert.bind(":expires") = expires;
872 insert.bind(":path") = cfString(path);
873 insert.bind(":authority").integer(authority);
874 insert.execute();
875 xact.commit();
876 }
877
878
879 //
880 // Perform update authorization processing.
881 // Throws an exception if authorization is denied.
882 //
883 static void authorizeUpdate(SecAssessmentFlags flags, CFDictionaryRef context)
884 {
885 AuthorizationRef authorization = NULL;
886
887 if (context)
888 if (CFTypeRef authkey = CFDictionaryGetValue(context, kSecAssessmentUpdateKeyAuthorization))
889 if (CFGetTypeID(authkey) == CFDataGetTypeID()) {
890 CFDataRef authdata = CFDataRef(authkey);
891 MacOSError::check(AuthorizationCreateFromExternalForm((AuthorizationExternalForm *)CFDataGetBytePtr(authdata), &authorization));
892 }
893 if (authorization == NULL)
894 MacOSError::check(AuthorizationCreate(NULL, NULL, kAuthorizationFlagDefaults, &authorization));
895
896 AuthorizationItem right[] = {
897 { "com.apple.security.assessment.update", 0, NULL, 0 }
898 };
899 AuthorizationRights rights = { sizeof(right) / sizeof(right[0]), right };
900 MacOSError::check(AuthorizationCopyRights(authorization, &rights, NULL,
901 kAuthorizationFlagExtendRights | kAuthorizationFlagInteractionAllowed, NULL));
902
903 MacOSError::check(AuthorizationFree(authorization, kAuthorizationFlagDefaults));
904 }
905
906
907 //
908 // Perform common argument normalizations for update operations
909 //
910 static void normalizeTarget(CFRef<CFTypeRef> &target, CFDictionary &context, std::string *signUnsigned)
911 {
912 // turn CFURLs into (designated) SecRequirements
913 if (target && CFGetTypeID(target) == CFURLGetTypeID()) {
914 CFRef<SecStaticCodeRef> code;
915 MacOSError::check(SecStaticCodeCreateWithPath(target.as<CFURLRef>(), kSecCSDefaultFlags, &code.aref()));
916 switch (OSStatus rc = SecCodeCopyDesignatedRequirement(code, kSecCSDefaultFlags, (SecRequirementRef *)&target.aref())) {
917 case noErr: {
918 // use the *default* DR to avoid unreasonably wide DRs opening up Gatekeeper to attack
919 CFRef<CFDictionaryRef> info;
920 MacOSError::check(SecCodeCopySigningInformation(code, kSecCSRequirementInformation, &info.aref()));
921 target = CFDictionaryGetValue(info, kSecCodeInfoImplicitDesignatedRequirement);
922 }
923 break;
924 case errSecCSUnsigned:
925 if (signUnsigned) {
926 // Ad-hoc sign the code temporarily so we can get its code requirement
927 CFRef<CFDataRef> signature = CFDataCreateMutable(NULL, 0);
928 CFRef<SecCodeSignerRef> signer;
929 CFTemp<CFDictionaryRef> arguments("{%O=%O, %O=#N}", kSecCodeSignerDetached, signature.get(), kSecCodeSignerIdentity);
930 MacOSError::check(SecCodeSignerCreate(arguments, kSecCSDefaultFlags, &signer.aref()));
931 MacOSError::check(SecCodeSignerAddSignature(signer, code, kSecCSDefaultFlags));
932 MacOSError::check(SecCodeSetDetachedSignature(code, signature, kSecCSDefaultFlags));
933 MacOSError::check(SecCodeCopyDesignatedRequirement(code, kSecCSDefaultFlags, (SecRequirementRef *)&target.aref()));
934 CFRef<CFDictionaryRef> info;
935 MacOSError::check(SecCodeCopySigningInformation(code, kSecCSInternalInformation, &info.aref()));
936 if (CFDataRef cdData = CFDataRef(CFDictionaryGetValue(info, kSecCodeInfoCodeDirectory)))
937 *signUnsigned = ((const CodeDirectory *)CFDataGetBytePtr(cdData))->screeningCode();
938 break;
939 }
940 MacOSError::check(rc);
941 case errSecCSSignatureFailed:
942 // recover certain cases of broken signatures (well, try)
943 if (codeInvalidityExceptions(code, NULL)) {
944 // Ad-hoc sign the code in place (requiring a writable subject). This requires root privileges.
945 CFRef<SecCodeSignerRef> signer;
946 CFTemp<CFDictionaryRef> arguments("{%O=#N}", kSecCodeSignerIdentity);
947 MacOSError::check(SecCodeSignerCreate(arguments, kSecCSDefaultFlags, &signer.aref()));
948 MacOSError::check(SecCodeSignerAddSignature(signer, code, kSecCSDefaultFlags));
949 MacOSError::check(SecCodeCopyDesignatedRequirement(code, kSecCSDefaultFlags, (SecRequirementRef *)&target.aref()));
950 break;
951 }
952 MacOSError::check(rc);
953 default:
954 MacOSError::check(rc);
955 }
956 if (context.get(kSecAssessmentUpdateKeyRemarks) == NULL) {
957 // no explicit remarks; add one with the path
958 CFRef<CFURLRef> path;
959 MacOSError::check(SecCodeCopyPath(code, kSecCSDefaultFlags, &path.aref()));
960 CFMutableDictionaryRef dict = makeCFMutableDictionary(context.get());
961 CFDictionaryAddValue(dict, kSecAssessmentUpdateKeyRemarks, CFTempString(cfString(path)));
962 context.take(dict);
963 }
964 }
965 }
966
967
968 //
969 // Process special overrides for invalidly signed code.
970 // This is the (hopefully minimal) concessions we make to keep hurting our customers
971 // for our own prior mistakes...
972 //
973 static bool codeInvalidityExceptions(SecStaticCodeRef code, CFMutableDictionaryRef result)
974 {
975 if (OSAIsRecognizedExecutableURL) {
976 CFRef<CFDictionaryRef> info;
977 MacOSError::check(SecCodeCopySigningInformation(code, kSecCSDefaultFlags, &info.aref()));
978 if (CFURLRef executable = CFURLRef(CFDictionaryGetValue(info, kSecCodeInfoMainExecutable))) {
979 SInt32 error;
980 if (OSAIsRecognizedExecutableURL(executable, &error)) {
981 if (result)
982 CFDictionaryAddValue(result,
983 kSecAssessmentAssessmentAuthorityOverride, CFSTR("ignoring known invalid applet signature"));
984 return true;
985 }
986 }
987 }
988 return false;
989 }
990
991
992 } // end namespace CodeSigning
993 } // end namespace Security