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