]> git.saurik.com Git - apple/libsecurity_codesigning.git/blob - lib/policyengine.cpp
libsecurity_codesigning-55037.15.tar.gz
[apple/libsecurity_codesigning.git] / 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 <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 && !overrideAssessment()) {
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 in the system database. This requires root privileges.
824 CFRef<SecCodeSignerRef> signer;
825 CFTemp<CFDictionaryRef> arguments("{%O=#N, %O=#N}", kSecCodeSignerDetached, kSecCodeSignerIdentity);
826 MacOSError::check(SecCodeSignerCreate(arguments, kSecCSDefaultFlags, &signer.aref()));
827 MacOSError::check(SecCodeSignerAddSignature(signer, code, kSecCSDefaultFlags));
828 MacOSError::check(SecCodeCopyDesignatedRequirement(code, kSecCSDefaultFlags, (SecRequirementRef *)&target.aref()));
829 break;
830 }
831 MacOSError::check(rc);
832 case errSecCSSignatureFailed:
833 // recover certain cases of broken signatures (well, try)
834 if (codeInvalidityExceptions(code, NULL)) {
835 // Ad-hoc sign the code in place (requiring a writable subject). This requires root privileges.
836 CFRef<SecCodeSignerRef> signer;
837 CFTemp<CFDictionaryRef> arguments("{%O=#N}", kSecCodeSignerIdentity);
838 MacOSError::check(SecCodeSignerCreate(arguments, kSecCSDefaultFlags, &signer.aref()));
839 MacOSError::check(SecCodeSignerAddSignature(signer, code, kSecCSDefaultFlags));
840 MacOSError::check(SecCodeCopyDesignatedRequirement(code, kSecCSDefaultFlags, (SecRequirementRef *)&target.aref()));
841 break;
842 }
843 MacOSError::check(rc);
844 default:
845 MacOSError::check(rc);
846 }
847 if (context.get(kSecAssessmentUpdateKeyRemarks) == NULL) {
848 // no explicit remarks; add one with the path
849 CFRef<CFURLRef> path;
850 MacOSError::check(SecCodeCopyPath(code, kSecCSDefaultFlags, &path.aref()));
851 CFMutableDictionaryRef dict = makeCFMutableDictionary(context.get());
852 CFDictionaryAddValue(dict, kSecAssessmentUpdateKeyRemarks, CFTempString(cfString(path)));
853 context.take(dict);
854 }
855 }
856 }
857
858
859 //
860 // Process special overrides for invalidly signed code.
861 // This is the (hopefully minimal) concessions we make to keep hurting our customers
862 // for our own prior mistakes...
863 //
864 static bool codeInvalidityExceptions(SecStaticCodeRef code, CFMutableDictionaryRef result)
865 {
866 if (OSAIsRecognizedExecutableURL) {
867 CFRef<CFDictionaryRef> info;
868 MacOSError::check(SecCodeCopySigningInformation(code, kSecCSDefaultFlags, &info.aref()));
869 if (CFURLRef executable = CFURLRef(CFDictionaryGetValue(info, kSecCodeInfoMainExecutable))) {
870 SInt32 error;
871 if (OSAIsRecognizedExecutableURL(executable, &error)) {
872 if (result)
873 CFDictionaryAddValue(result,
874 kSecAssessmentAssessmentAuthorityOverride, CFSTR("ignoring known invalid applet signature"));
875 return true;
876 }
877 }
878 }
879 return false;
880 }
881
882
883 } // end namespace CodeSigning
884 } // end namespace Security