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