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