2 * Copyright (c) 2011-2012 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
23 #include "policyengine.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>
39 #include "codedirectory.h"
40 #include "csutilities.h"
41 #include "StaticCode.h"
43 #include <CoreServices/CoreServicesPriv.h>
44 #include "SecCodePriv.h"
45 #undef check // Macro! Yech.
48 #include <OpenScriptingUtilPriv.h>
53 namespace CodeSigning
{
55 static const double NEGATIVE_HOLD
= 60.0/86400; // 60 seconds to cache negative outcomes
57 static const char RECORDER_DIR
[] = "/tmp/gke-"; // recorder mode destination for detached signatures
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
65 static void authorizeUpdate(SecAssessmentFlags flags
, CFDictionaryRef context
);
66 static bool codeInvalidityExceptions(SecStaticCodeRef code
, CFMutableDictionaryRef result
);
67 static CFTypeRef
installerPolicy() CF_RETURNS_RETAINED
;
73 PolicyEngine::PolicyEngine()
74 : PolicyDatabase(NULL
, SQLITE_OPEN_READWRITE
| SQLITE_OPEN_CREATE
)
78 PolicyEngine::~PolicyEngine()
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"
87 gkbis_invoke_collector(const char *path
)
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
) {
96 xpc_connection_resume(connection
);
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
);
112 /* Wait until the connection is canceled. */
113 dispatch_group_wait(group
, DISPATCH_TIME_FOREVER
);
114 dispatch_release(queue
);
115 dispatch_release(group
);
119 // Top-level evaluation driver
121 void PolicyEngine::evaluate(CFURLRef path
, AuthorityType type
, SecAssessmentFlags flags
, CFDictionaryRef context
, CFMutableDictionaryRef result
)
124 installExplicitSet(gkeAuthFile
, gkeSigsFile
);
127 case kAuthorityExecute
:
128 gkbis_invoke_collector(cfString(path
).c_str());
129 evaluateCode(path
, kAuthorityExecute
, flags
, context
, result
, true);
131 case kAuthorityInstall
:
132 evaluateInstall(path
, flags
, context
, result
);
134 case kAuthorityOpenDoc
:
135 evaluateDocOpen(path
, flags
, context
, result
);
138 MacOSError::throwMe(errSecCSInvalidAttributeValues
);
144 static std::string
createWhitelistScreen(char type
, SHA1
&hash
)
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
]);
155 void PolicyEngine::evaluateCodeItem(SecStaticCodeRef code
, CFURLRef path
, AuthorityType type
, SecAssessmentFlags flags
, bool nested
, CFMutableDictionaryRef result
)
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
);
164 SQLite3::int64 latentID
= 0; // first (highest priority) disabled matching ID
165 std::string latentLabel
; // ... and associated label, if any
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];
178 CFRef
<SecRequirementRef
> requirement
;
179 MacOSError::check(SecRequirementCreateWithString(CFTempString(reqString
), kSecCSDefaultFlags
, &requirement
.aref()));
180 switch (OSStatus rc
= SecStaticCodeCheckValidity(code
, kSecCSDefaultFlags
, requirement
)) {
182 break; // rule match; process below
183 case errSecCSReqFailed
:
184 continue; // rule does not apply
186 MacOSError::throwMe(rc
); // general error; pass to caller
189 // if this rule is disabled, skip it but record the first matching one for posterity
190 if (disabled
&& latentID
== 0) {
192 latentLabel
= label
? label
: "";
196 // current rule is first rule (in priority order) that matched. Apply it
197 if (nested
) // success, nothing to record
200 CFRef
<CFDictionaryRef
> info
; // as needed
201 if (flags
& kSecAssessmentFlagRequestOrigin
) {
203 MacOSError::check(SecCodeCopySigningInformation(code
, kSecCSSigningInformation
, &info
.aref()));
204 if (CFArrayRef chain
= CFArrayRef(CFDictionaryGetValue(info
, kSecCodeInfoCertificates
)))
205 setOrigin(chain
, result
);
207 if (!(ruleFlags
& kAuthorityFlagInhibitCache
) && !(flags
& kSecAssessmentFlagNoCache
)) { // cache inhibit
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
);
219 if (SYSPOLICY_ASSESS_OUTCOME_ACCEPT_ENABLED()) {
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
);
226 if (SYSPOLICY_ASSESS_OUTCOME_DENY_ENABLED() || SYSPOLICY_RECORDER_MODE_ENABLED()) {
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
);
236 cfadd(result
, "{%O=%B}", kSecAssessmentAssessmentVerdict
, allow
);
237 addAuthority(flags
, result
, label
, id
);
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
);
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);
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
);
262 bool PolicyEngine::temporarySigning(SecStaticCodeRef code
, AuthorityType type
, CFURLRef path
, SecAssessmentFlags matchFlags
)
264 if (matchFlags
== 0) { // playback; consult authority table for matches
265 DiskRep
*rep
= SecStaticCode::requiredStatic(code
)->diskRep();
267 if (CFRef
<CFDataRef
> info
= rep
->component(cdInfoSlot
)) {
269 hash
.update(CFDataGetBytePtr(info
), CFDataGetLength(info
));
270 screen
= createWhitelistScreen('I', hash
);
271 } else if (rep
->mainExecutableImage()) {
275 hashFileData(rep
->mainExecutablePath().c_str(), &hash
);
276 screen
= createWhitelistScreen('M', hash
);
278 SQLite::Statement
query(*this,
279 "SELECT flags FROM authority "
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
289 matchFlags
= SQLite3::int64(query
[0]);
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
));
301 SecRequirementRef dr
= NULL
;
302 SecCodeCopyDesignatedRequirement(code
, kSecCSDefaultFlags
, &dr
);
303 CFStringRef drs
= NULL
;
304 SecRequirementCopyString(dr
, kSecCSDefaultFlags
, &drs
);
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";
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());
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
);
328 return true; // it worked; we're now (well) signed
337 // Read from disk, evaluate properly, cache as indicated. The whole thing, so far.
339 void PolicyEngine::evaluateCode(CFURLRef path
, AuthorityType type
, SecAssessmentFlags flags
, CFDictionaryRef context
, CFMutableDictionaryRef result
, bool handleUnsigned
)
341 FileQuarantine
qtn(cfString(path
).c_str());
342 if (qtn
.flag(QTN_FLAG_HARD
))
343 MacOSError::throwMe(errSecCSFileHardQuarantined
);
345 CFCopyRef
<SecStaticCodeRef
> code
;
346 MacOSError::check(SecStaticCodeCreateWithPath(path
, kSecCSDefaultFlags
, &code
.aref()));
348 SecCSFlags validationFlags
= kSecCSEnforceRevocationChecks
;
350 // first, perform a shallow check
351 OSStatus rc
= SecStaticCodeCheckValidity(code
, validationFlags
, NULL
);
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
);
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
;
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
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)
380 switch (rc
= SecStaticCodeCheckValidity(code
, validationFlags
| kSecCSCheckNestedCode
, NULL
)) {
381 case errSecSuccess
: // continue below
383 case errSecCSUnsigned
:
384 cfadd(result
, "{%O=#F}", kSecAssessmentAssessmentVerdict
);
385 addAuthority(flags
, result
, "no usable signature");
387 case errSecCSVetoed
: // nested code rejected by rule book; result was filled out there
390 MacOSError::throwMe(rc
);
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.
400 void PolicyEngine::evaluateInstall(CFURLRef path
, SecAssessmentFlags flags
, CFDictionaryRef context
, CFMutableDictionaryRef result
)
402 const AuthorityType type
= kAuthorityInstall
;
404 Xar
xar(cfString(path
).c_str());
406 // follow the code signing path
407 evaluateCode(path
, type
, flags
, context
, result
, true);
411 // check for recent explicit approval, using a bookmark's FileResourceIdentifierKey
412 if (CFRef
<CFDataRef
> bookmark
= cfLoadFile(lastApprovedFile
)) {
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
, ¤tIdent
.aref(), NULL
))
421 if (CFURLCopyResourcePropertyForKey(path
, kCFURLContentModificationDateKey
, ¤tMod
.aref(), NULL
))
422 if (CFEqual(savedIdent
, currentIdent
) && CFEqual(savedMod
, currentMod
)) {
423 cfadd(result
, "{%O=#T}", kSecAssessmentAssessmentVerdict
);
424 addAuthority(flags
, result
, "explicit preference");
430 SQLite3::int64 latentID
= 0; // first (highest priority) disabled matching ID
431 std::string latentLabel
; // ... and associated label, if any
432 if (!xar
.isSigned()) {
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");
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
));
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
));
453 if (flags
& kSecAssessmentFlagRequestOrigin
)
454 setOrigin(chain
, result
);
456 switch (trustResult
) {
457 case kSecTrustResultProceed
:
458 case kSecTrustResultUnspecified
:
463 MacOSError::check(SecTrustGetCssmResultCode(trust
, &rc
));
464 MacOSError::throwMe(rc
);
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];
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
486 case errSecCSReqFailed
: // requirement missed, but otherwise okay
488 default: // broken in some way; all tests will fail like this so bail out
489 MacOSError::throwMe(rc
);
497 continue; // the loop
500 if (SYSPOLICY_ASSESS_OUTCOME_ACCEPT_ENABLED() || SYSPOLICY_ASSESS_OUTCOME_DENY_ENABLED()) {
502 SYSPOLICY_ASSESS_OUTCOME_ACCEPT(cfString(path
).c_str(), type
, label
, NULL
);
504 SYSPOLICY_ASSESS_OUTCOME_DENY(cfString(path
).c_str(), type
, label
, NULL
);
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
);
513 if (SYSPOLICY_ASSESS_OUTCOME_DEFAULT_ENABLED())
514 SYSPOLICY_ASSESS_OUTCOME_DEFAULT(cfString(path
).c_str(), type
, latentLabel
.c_str(), NULL
);
516 // no applicable authority. Deny by default
517 cfadd(result
, "{%O=#F}", kSecAssessmentAssessmentVerdict
);
518 addAuthority(flags
, result
, latentLabel
.c_str(), latentID
);
523 // Create a suitable policy array for verification of installer signatures.
525 static SecPolicyRef
makeCRLPolicy()
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();
538 static SecPolicyRef
makeOCSPPolicy()
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();
551 static CFTypeRef
installerPolicy()
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());
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.
564 void PolicyEngine::evaluateDocOpen(CFURLRef path
, SecAssessmentFlags flags
, CFDictionaryRef context
, CFMutableDictionaryRef result
)
567 if (CFStringRef riskCategory
= CFStringRef(CFDictionaryGetValue(context
, kLSDownloadRiskCategoryKey
))) {
568 FileQuarantine
qtn(cfString(path
).c_str());
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
583 evaluateCode(path
, kAuthorityExecute
, flags
, context
, result
, false);
585 // some documents can't be code signed, so this may be quite benign
588 if (CFDictionaryGetValue(result
, kSecAssessmentAssessmentVerdict
) == NULL
) { // no code signature to help us out
589 cfadd(result
, "{%O=#F}", kSecAssessmentAssessmentVerdict
);
590 addAuthority(flags
, result
, "_XProtect");
592 addToAuthority(result
, kLSDownloadRiskCategoryKey
, riskCategory
);
596 // insufficient information from LS - deny by default
597 cfadd(result
, "{%O=#F}", kSecAssessmentAssessmentVerdict
);
598 addAuthority(flags
, result
, "Insufficient Context");
603 // Result-creation helpers
605 void PolicyEngine::addAuthority(SecAssessmentFlags flags
, CFMutableDictionaryRef parent
, const char *label
, SQLite::int64 row
, CFTypeRef cacheInfo
)
607 CFRef
<CFMutableDictionaryRef
> auth
= makeCFMutableDictionary();
608 if (label
&& label
[0])
609 cfadd(auth
, "{%O=%s}", kSecAssessmentAssessmentSource
, label
);
611 CFDictionaryAddValue(auth
, kSecAssessmentAssessmentAuthorityRow
, CFTempNumber(row
));
612 if (overrideAssessment(flags
))
613 CFDictionaryAddValue(auth
, kSecAssessmentAssessmentAuthorityOverride
, kDisabledOverride
);
615 CFDictionaryAddValue(auth
, kSecAssessmentAssessmentFromCache
, cacheInfo
);
616 CFDictionaryAddValue(parent
, kSecAssessmentAssessmentAuthority
, auth
);
619 void PolicyEngine::addToAuthority(CFMutableDictionaryRef parent
, CFStringRef key
, CFTypeRef value
)
621 CFMutableDictionaryRef authority
= CFMutableDictionaryRef(CFDictionaryGetValue(parent
, kSecAssessmentAssessmentAuthority
));
623 CFDictionaryAddValue(authority
, key
, value
);
628 // Add a rule to the policy database
630 CFDictionaryRef
PolicyEngine::add(CFTypeRef inTarget
, AuthorityType type
, SecAssessmentFlags flags
, CFDictionaryRef context
)
632 // default type to execution
633 if (type
== kAuthorityInvalid
)
634 type
= kAuthorityExecute
;
636 authorizeUpdate(flags
, context
);
637 CFDictionary
ctx(context
, errSecCSInvalidAttributeValues
);
638 CFCopyRef
<CFTypeRef
> target
= inTarget
;
639 CFRef
<CFDataRef
> bookmark
= NULL
;
640 std::string filter_unsigned
;
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
);
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
));
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
);
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());
674 return cfmake
<CFDictionaryRef
>("{%O=%O}", kSecAssessmentAssessmentAuthorityOverride
, CFSTR("unable to set quarantine"));
679 // if we now have anything else, we're busted
680 if (!target
|| CFGetTypeID(target
) != SecRequirementGetTypeID())
681 MacOSError::throwMe(errSecCSInvalidObjectRef
);
686 double expires
= never
;
688 SQLite::uint64 dbFlags
= kAuthorityFlagWhitelistV2
;
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
);
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
;
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
);
720 SQLite::int64 newRow
= this->lastInsert();
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
);
727 this->purgeObjects(priority
);
729 notify_post(kNotifySecAssessmentUpdate
);
730 return cfmake
<CFDictionaryRef
>("{%O=%d}", kSecAssessmentUpdateKeyRow
, newRow
);
734 CFDictionaryRef
PolicyEngine::remove(CFTypeRef target
, AuthorityType type
, SecAssessmentFlags flags
, CFDictionaryRef context
)
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());
747 return manipulateRules("DELETE FROM authority", target
, type
, flags
, context
);
750 CFDictionaryRef
PolicyEngine::enable(CFTypeRef target
, AuthorityType type
, SecAssessmentFlags flags
, CFDictionaryRef context
)
752 return manipulateRules("UPDATE authority SET disabled = 0", target
, type
, flags
, context
);
755 CFDictionaryRef
PolicyEngine::disable(CFTypeRef target
, AuthorityType type
, SecAssessmentFlags flags
, CFDictionaryRef context
)
757 return manipulateRules("UPDATE authority SET disabled = 1", target
, type
, flags
, context
);
760 CFDictionaryRef
PolicyEngine::find(CFTypeRef target
, AuthorityType type
, SecAssessmentFlags flags
, CFDictionaryRef context
)
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()
786 CFDictionaryAddValue(rule
, kSecAssessmentRuleKeyLabel
, CFTempString(label
));
788 CFDictionaryAddValue(rule
, kSecAssessmentRuleKeyRemarks
, CFTempString(remarks
));
789 if (expires
!= never
)
790 CFDictionaryAddValue(rule
, kSecAssessmentRuleKeyExpires
, CFRef
<CFDateRef
>(julianToDate(expires
)));
792 CFDictionaryAddValue(rule
, kSecAssessmentRuleKeyDisabled
, CFTempNumber(disabled
));
794 CFDictionaryAddValue(rule
, kSecAssessmentRuleKeyBookmark
, bookmark
);
795 CFArrayAppendValue(found
, rule
);
797 if (CFArrayGetCount(found
) == 0)
798 MacOSError::throwMe(errSecCSNoMatches
);
799 return cfmake
<CFDictionaryRef
>("{%O=%O}", kSecAssessmentUpdateKeyFound
, found
.get());
803 CFDictionaryRef
PolicyEngine::update(CFTypeRef target
, SecAssessmentFlags flags
, CFDictionaryRef context
)
806 installExplicitSet(gkeAuthFile
, gkeSigsFile
);
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
);
822 MacOSError::throwMe(errSecCSInvalidAttributeValues
);
824 result
= makeCFDictionary(0); // success, no details
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.
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
/* = "" */)
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
);
842 if (CFStringRef lab
= ctx
.get
<CFStringRef
>(kSecAssessmentUpdateKeyLabel
))
843 label
= cfString(CFStringRef(lab
));
847 if (type
== kAuthorityInvalid
) {
848 action
.query(phrase
+ suffix
);
850 action
.query(phrase
+ " WHERE " + table
+ ".type = :type" + suffix
);
851 action
.bind(":type").integer(type
);
853 } else { // have label
854 if (type
== kAuthorityInvalid
) {
855 action
.query(phrase
+ " WHERE " + table
+ ".label = :label" + suffix
);
857 action
.query(phrase
+ " WHERE " + table
+ ".type = :type AND " + table
+ ".label = :label" + suffix
);
858 action
.bind(":type").integer(type
);
860 action
.bind(":label") = label
;
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();
874 MacOSError::throwMe(errSecCSInvalidObjectRef
);
879 // Execute an atomic change to existing records in the authority table.
881 CFDictionaryRef
PolicyEngine::manipulateRules(const std::string
&stanza
,
882 CFTypeRef inTarget
, AuthorityType type
, SecAssessmentFlags flags
, CFDictionaryRef context
)
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
);
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.
893 this->purgeObjects(1.0E100
);
895 notify_post(kNotifySecAssessmentUpdate
);
896 return cfmake
<CFDictionaryRef
>("{%O=%d}", kSecAssessmentUpdateKeyCount
, changes
);
898 // no change; return an error
899 MacOSError::throwMe(errSecCSNoMatches
);
904 // Fill in extra information about the originator of cryptographic credentials found - if any
906 void PolicyEngine::setOrigin(CFArrayRef chain
, CFMutableDictionaryRef result
)
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
);
919 // Take an assessment outcome and record it in the object cache
921 void PolicyEngine::recordOutcome(SecStaticCodeRef code
, bool allow
, AuthorityType type
, double expires
, SQLite::int64 authority
)
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()));
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"
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
);
948 // Record a UI failure record after proper validation of the caller
950 void PolicyEngine::recordFailure(CFDictionaryRef info
)
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
);
960 // Perform update authorization processing.
961 // Throws an exception if authorization is denied.
963 static void authorizeUpdate(SecAssessmentFlags flags
, CFDictionaryRef context
)
965 AuthorizationRef authorization
= NULL
;
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
));
973 if (authorization
== NULL
)
974 MacOSError::check(AuthorizationCreate(NULL
, NULL
, kAuthorizationFlagDefaults
, &authorization
));
976 AuthorizationItem right
[] = {
977 { "com.apple.security.assessment.update", 0, NULL
, 0 }
979 AuthorizationRights rights
= { sizeof(right
) / sizeof(right
[0]), right
};
980 MacOSError::check(AuthorizationCopyRights(authorization
, &rights
, NULL
,
981 kAuthorizationFlagExtendRights
| kAuthorizationFlagInteractionAllowed
, NULL
));
983 MacOSError::check(AuthorizationFree(authorization
, kAuthorizationFlagDefaults
));
988 // Perform common argument normalizations for update operations
990 void PolicyEngine::normalizeTarget(CFRef
<CFTypeRef
> &target
, AuthorityType type
, CFDictionary
&context
, std::string
*signUnsigned
)
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
);
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();
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()));
1026 MacOSError::check(rc
);
1028 MacOSError::check(rc
);
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
)));
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...
1047 static bool codeInvalidityExceptions(SecStaticCodeRef code
, CFMutableDictionaryRef result
)
1049 if (OSAIsRecognizedExecutableURL
) {
1050 CFRef
<CFDictionaryRef
> info
;
1051 MacOSError::check(SecCodeCopySigningInformation(code
, kSecCSDefaultFlags
, &info
.aref()));
1052 if (CFURLRef executable
= CFURLRef(CFDictionaryGetValue(info
, kSecCodeInfoMainExecutable
))) {
1054 if (OSAIsRecognizedExecutableURL(executable
, &error
)) {
1056 CFDictionaryAddValue(result
,
1057 kSecAssessmentAssessmentAuthorityOverride
, CFSTR("ignoring known invalid applet signature"));
1066 } // end namespace CodeSigning
1067 } // end namespace Security