]> git.saurik.com Git - apple/security.git/blobdiff - OSX/libsecurity_codesigning/lib/policyengine.cpp
Security-59306.80.4.tar.gz
[apple/security.git] / OSX / libsecurity_codesigning / lib / policyengine.cpp
index 0823943999175532cd394da0253c82435678fe06..a29937164d7d5bf1ea3c686c8278ac1bc6013e85 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011-2014 Apple Inc. All Rights Reserved.
+ * Copyright (c) 2011-2016 Apple Inc. All Rights Reserved.
  * 
  * @APPLE_LICENSE_HEADER_START@
  * 
 #include "diskrep.h"
 #include "codedirectory.h"
 #include "csutilities.h"
+#include "notarization.h"
 #include "StaticCode.h"
 
 #include <CoreServices/CoreServicesPriv.h>
 #include "SecCodePriv.h"
 #undef check // Macro! Yech.
 
-extern "C" {
-#include <OpenScriptingUtilPriv.h>
-}
-
-
 namespace Security {
 namespace CodeSigning {
 
@@ -63,7 +59,6 @@ enum {
 
 
 static void authorizeUpdate(SecAssessmentFlags flags, CFDictionaryRef context);
-static bool codeInvalidityExceptions(SecStaticCodeRef code, CFMutableDictionaryRef result);
 static CFTypeRef installerPolicy() CF_RETURNS_RETAINED;
 
 
@@ -73,10 +68,18 @@ static CFTypeRef installerPolicy() CF_RETURNS_RETAINED;
 PolicyEngine::PolicyEngine()
        : PolicyDatabase(NULL, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE)
 {
+       try {
+               mOpaqueWhitelist = new OpaqueWhitelist();
+       } catch (...) {
+               mOpaqueWhitelist = NULL;
+               secerror("Failed opening the gkopaque database.");
+       }
 }
 
 PolicyEngine::~PolicyEngine()
-{ }
+{
+       delete mOpaqueWhitelist;
+}
 
 
 //
@@ -100,16 +103,56 @@ void PolicyEngine::evaluate(CFURLRef path, AuthorityType type, SecAssessmentFlag
 }
 
 
-static std::string createWhitelistScreen(char type, SHA1 &hash)
+//
+// Create GKE whitelist filter screens.
+// These are strings that are used to determine quickly whether unsigned code may
+// have a GKE-style whitelist entry in the authority database. The idea is to make
+// up a decent hash quickly.
+//
+// Note: We continue to use SHA1 here for compatibility of existing GKE entries.
+// These are a prescreen, backed up by code signature checks later on. Use of SHA1 here is not a security problem.
+//
+static std::string createWhitelistScreen(char type, const Byte *digest, size_t length)
 {
-       SHA1::Digest digest;
-       hash.finish(digest);
-       char buffer[2*SHA1::digestLength + 2] = { type };
-       for (size_t n = 0; n < SHA1::digestLength; n++)
+       char buffer[2*length + 2];
+       buffer[0] = type;
+       for (size_t n = 0; n < length; n++)
                sprintf(buffer + 1 + 2*n, "%02.2x", digest[n]);
        return buffer;
 }
 
+static std::string createWhitelistScreen(SecStaticCodeRef code)
+{
+       DiskRep *rep = SecStaticCode::requiredStatic(code)->diskRep();
+       std::string screen;
+       if (CFRef<CFDataRef> info = rep->component(cdInfoSlot)) {
+               // has an Info.plist - hash it
+               SHA1 hash;
+               hash.update(CFDataGetBytePtr(info), CFDataGetLength(info));
+               SHA1::Digest digest;
+               hash.finish(digest);
+               return createWhitelistScreen('I', digest, sizeof(digest));
+       } else if (CFRef<CFDataRef> repSpecific = rep->component(cdRepSpecificSlot)) {
+               // has a rep-specific slot - hash that (this catches disk images cheaply)
+               // got invented after SHA-1 deprecation, so we'll use SHA256, which is the new default
+               CCHashInstance hash(kCCDigestSHA256);
+               hash.update(CFDataGetBytePtr(repSpecific), CFDataGetLength(repSpecific));
+               Byte digest[256/8];
+               hash.finish(digest);
+               return createWhitelistScreen('R', digest, sizeof(digest));
+       } else if (rep->mainExecutableImage()) {
+               // stand-alone Mach-O executables are always candidates
+               return "N";
+       } else {
+               // if everything else fails, hash the (single) file
+               SHA1 hash;
+               hashFileData(rep->mainExecutablePath().c_str(), &hash);
+               SHA1::Digest digest;
+               hash.finish(digest);
+               return createWhitelistScreen('M', digest, sizeof(digest));
+       }
+}
+
 
 void PolicyEngine::evaluateCodeItem(SecStaticCodeRef code, CFURLRef path, AuthorityType type, SecAssessmentFlags flags, bool nested, CFMutableDictionaryRef result)
 {
@@ -123,6 +166,7 @@ void PolicyEngine::evaluateCodeItem(SecStaticCodeRef code, CFURLRef path, Author
        SQLite3::int64 latentID = 0;            // first (highest priority) disabled matching ID
        std::string latentLabel;                        // ... and associated label, if any
 
+    secdebug("gk", "evaluateCodeItem type=%d flags=0x%x nested=%d path=%s", type, int(flags), nested, cfString(path).c_str());
        while (query.nextRow()) {
                bool allow = int(query[0]);
                const char *reqString = query[1];
@@ -133,7 +177,8 @@ void PolicyEngine::evaluateCodeItem(SecStaticCodeRef code, CFURLRef path, Author
                SQLite3::int64 disabled = query[6];
 //             const char *filter = query[7];
 //             const char *remarks = query[8];
-               
+
+               secdebug("gk", "considering rule %d(%s) requirement %s", int(id), label ? label : "UNLABELED", reqString);
                CFRef<SecRequirementRef> requirement;
                MacOSError::check(SecRequirementCreateWithString(CFTempString(reqString), kSecCSDefaultFlags, &requirement.aref()));
                switch (OSStatus rc = SecStaticCodeCheckValidity(code, kSecCSBasicValidateOnly | kSecCSCheckGatekeeperArchitectures, requirement)) {
@@ -147,15 +192,20 @@ void PolicyEngine::evaluateCodeItem(SecStaticCodeRef code, CFURLRef path, Author
                        MacOSError::throwMe(rc);        // general error; pass to caller
                }
                
-               // if this rule is disabled, skip it but record the first matching one for posterity
-               if (disabled && latentID == 0) {
-                       latentID = id;
-                       latentLabel = label ? label : "";
+               // If this rule is disabled, do not continue any further and just continue iterating
+               // until we find one that is enabled.
+               if (disabled) {
+                       // ...but always record the first matching rule for informational purposes.
+                       if (latentID == 0) {
+                               latentID = id;
+                               latentLabel = label ? label : "";
+                       }
                        continue;
                }
-               
+
                // current rule is first rule (in priority order) that matched. Apply it
-               if (nested)     // success, nothing to record
+        secnotice("gk", "rule %d applies - allow=%d", int(id), allow);
+               if (nested && allow)                    // success, nothing to record
                        return;
 
                CFRef<CFDictionaryRef> info;    // as needed
@@ -195,11 +245,12 @@ void PolicyEngine::evaluateCodeItem(SecStaticCodeRef code, CFURLRef path, Author
                        }
                }
                cfadd(result, "{%O=%B}", kSecAssessmentAssessmentVerdict, allow);
-               addAuthority(flags, result, label, id);
+               addAuthority(flags, result, label, id, NULL, false, ruleFlags);
                return;
        }
        
        // no applicable authority (but signed, perhaps temporarily). Deny by default
+    secnotice("gk", "rejecting due to lack of matching active rule");
        CFRef<CFDictionaryRef> info;
        MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref()));
        if (flags & kSecAssessmentFlagRequestOrigin) {
@@ -218,49 +269,61 @@ void PolicyEngine::evaluateCodeItem(SecStaticCodeRef code, CFURLRef path, Author
        cfadd(result, "{%O=%B}", kSecAssessmentAssessmentVerdict, false);
        addAuthority(flags, result, latentLabel.c_str(), latentID);
 }
-       
+
+CFDictionaryRef PolicyEngine::opaqueWhitelistValidationConditionsFor(SecStaticCodeRef code)
+{
+        return (mOpaqueWhitelist != NULL) ? mOpaqueWhitelist->validationConditionsFor(code) : NULL;
+}
+
+bool PolicyEngine::opaqueWhiteListContains(SecStaticCodeRef code, SecAssessmentFeedback feedback, OSStatus reason)
+{
+       return (mOpaqueWhitelist != NULL) ? mOpaqueWhitelist->contains(code, feedback, reason) : false;
+}
+
+void PolicyEngine::opaqueWhitelistAdd(SecStaticCodeRef code)
+{
+       if (mOpaqueWhitelist) {
+               mOpaqueWhitelist->add(code);
+       }
+}
 
 void PolicyEngine::adjustValidation(SecStaticCodeRef code)
 {
-       CFRef<CFDictionaryRef> conditions = mOpaqueWhitelist.validationConditionsFor(code);
+       CFRef<CFDictionaryRef> conditions = opaqueWhitelistValidationConditionsFor(code);
        SecStaticCodeSetValidationConditions(code, conditions);
 }
 
 
 bool PolicyEngine::temporarySigning(SecStaticCodeRef code, AuthorityType type, CFURLRef path, SecAssessmentFlags matchFlags)
 {
-       if (matchFlags == 0) {  // playback; consult authority table for matches
-               DiskRep *rep = SecStaticCode::requiredStatic(code)->diskRep();
-               std::string screen;
-               if (CFRef<CFDataRef> info = rep->component(cdInfoSlot)) {
-                       SHA1 hash;
-                       hash.update(CFDataGetBytePtr(info), CFDataGetLength(info));
-                       screen = createWhitelistScreen('I', hash);
-               } else if (rep->mainExecutableImage()) {
-                       screen = "N";
-               } else {
-                       SHA1 hash;
-                       hashFileData(rep->mainExecutablePath().c_str(), &hash);
-                       screen = createWhitelistScreen('M', hash);
-               }
-               SQLite::Statement query(*this,
-                       "SELECT flags FROM authority "
-                       "WHERE type = :type"
-                       " AND NOT flags & :flag"
-                       " AND CASE WHEN filter_unsigned IS NULL THEN remarks = :remarks ELSE filter_unsigned = :screen END");
-               query.bind(":type").integer(type);
-               query.bind(":flag").integer(kAuthorityFlagDefault);
-               query.bind(":screen") = screen;
-               query.bind(":remarks") = cfString(path);
-               if (!query.nextRow())   // guaranteed no matching rule
-                       return false;
-               matchFlags = SQLite3::int64(query[0]);
-       }
+    secnotice("gk", "temporarySigning type=%d matchFlags=0x%x path=%s", type, int(matchFlags), cfString(path).c_str());
+
+    // see if we have a screened record to take matchFlags from
+    std::string screen = createWhitelistScreen(code);
+    SQLite::Statement query(*this,
+        "SELECT flags FROM authority "
+        "WHERE type = :type"
+        " AND NOT flags & :flag"
+        " AND CASE WHEN filter_unsigned IS NULL THEN remarks = :remarks ELSE filter_unsigned = :screen END");
+    query.bind(":type").integer(type);
+    query.bind(":flag").integer(kAuthorityFlagDefault);
+    query.bind(":screen") = screen;
+    query.bind(":remarks") = cfString(path);
+    secdebug("gk", "match screen=%s", screen.c_str());
+    if (query.nextRow())       // got a matching rule
+        matchFlags = SQLite3::int64(query[0]);
+    else if (matchFlags == 0)  // lazy and no match
+        return false;
+    secdebug("gk", "matchFlags found=0x%x", int(matchFlags));
 
        try {
                // ad-hoc sign the code and attach the signature
                CFRef<CFDataRef> signature = CFDataCreateMutable(NULL, 0);
-               CFTemp<CFDictionaryRef> arguments("{%O=%O, %O=#N}", kSecCodeSignerDetached, signature.get(), kSecCodeSignerIdentity);
+               CFTemp<CFMutableDictionaryRef> arguments("{%O=%O, %O=#N, %O=%d}", kSecCodeSignerDetached, signature.get(), kSecCodeSignerIdentity,
+                       kSecCodeSignerDigestAlgorithm, (matchFlags & kAuthorityFlagWhitelistSHA256) ? kSecCodeSignatureHashSHA256 : kSecCodeSignatureHashSHA1);
+               // for modern whitelist entries, neuter the identifier since it may be derived from the filename
+               if (matchFlags & kAuthorityFlagWhitelistSHA256)
+                       CFDictionaryAddValue(arguments, kSecCodeSignerIdentifier, CFSTR("ADHOC"));
                CFRef<SecCodeSignerRef> signer;
                MacOSError::check(SecCodeSignerCreate(arguments, (matchFlags & kAuthorityFlagWhitelistV2) ? kSecCSSignOpaque : kSecCSSignV1, &signer.aref()));
                MacOSError::check(SecCodeSignerAddSignature(signer, code, kSecCSDefaultFlags));
@@ -270,7 +333,8 @@ bool PolicyEngine::temporarySigning(SecStaticCodeRef code, AuthorityType type, C
                SecCodeCopyDesignatedRequirement(code, kSecCSDefaultFlags, &dr);
                CFStringRef drs = NULL;
                SecRequirementCopyString(dr, kSecCSDefaultFlags, &drs);
-
+        secnotice("gk", "successfully created temporary signature - requirement=%s", cfString(drs).c_str());
+        
                // if we're in GKE recording mode, save that signature and report its location
                if (SYSPOLICY_RECORDER_MODE_ENABLED()) {
                        int status = recorder_code_unable;      // ephemeral signature (not recorded)
@@ -307,12 +371,23 @@ bool PolicyEngine::temporarySigning(SecStaticCodeRef code, AuthorityType type, C
 void PolicyEngine::evaluateCode(CFURLRef path, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result, bool handleUnsigned)
 {
        // not really a Gatekeeper function... but reject all "hard quarantined" files because they were made from sandboxed sources without download privilege
-       FileQuarantine qtn(cfString(path).c_str());
-       if (qtn.flag(QTN_FLAG_HARD))
-               MacOSError::throwMe(errSecCSFileHardQuarantined);
+    if (type == kAuthorityExecute) {
+        FileQuarantine qtn(cfString(path).c_str());
+        if (qtn.flag(QTN_FLAG_HARD))
+            MacOSError::throwMe(errSecCSFileHardQuarantined);
+    }
+       
+       // hack: if caller passed a UTI, use that to turn off app-only checks for some well-known ones
+       bool appOk = false;
+       if (CFStringRef uti = CFStringRef(CFDictionaryGetValue(context, kSecAssessmentContextKeyUTI))) {
+               appOk = CFEqual(uti, CFSTR("com.apple.systempreference.prefpane"))
+                       || CFEqual(uti, CFSTR("com.apple.systempreference.screen-saver"))
+                       || CFEqual(uti, CFSTR("com.apple.systempreference.screen-slide-saver"))
+                       || CFEqual(uti, CFSTR("com.apple.menu-extra"));
+       }
        
        CFCopyRef<SecStaticCodeRef> code;
-       MacOSError::check(SecStaticCodeCreateWithPath(path, kSecCSDefaultFlags, &code.aref()));
+       MacOSError::check(SecStaticCodeCreateWithPath(path, kSecCSDefaultFlags | kSecCSForceOnlineNotarizationCheck, &code.aref()));
        
        SecCSFlags validationFlags = kSecCSEnforceRevocationChecks | kSecCSCheckAllArchitectures;
        if (!(flags & kSecAssessmentFlagAllowWeak))
@@ -322,20 +397,14 @@ void PolicyEngine::evaluateCode(CFURLRef path, AuthorityType type, SecAssessment
        // deal with a very special case (broken 10.6/10.7 Applet bundles)
        OSStatus rc = SecStaticCodeCheckValidity(code, validationFlags | kSecCSBasicValidateOnly, NULL);
        if (rc == errSecCSSignatureFailed) {
-               if (!codeInvalidityExceptions(code, result)) {  // invalidly signed, no exceptions -> error
-                       if (SYSPOLICY_ASSESS_OUTCOME_BROKEN_ENABLED())
-                               SYSPOLICY_ASSESS_OUTCOME_BROKEN(cfString(path).c_str(), type, false);
-                       MacOSError::throwMe(rc);
-               }
-               // recognized exception - treat as unsigned
-               if (SYSPOLICY_ASSESS_OUTCOME_BROKEN_ENABLED())
-                       SYSPOLICY_ASSESS_OUTCOME_BROKEN(cfString(path).c_str(), type, true);
-               rc = errSecCSUnsigned;
+               MacOSError::throwMe(rc);
        }
 
        // ad-hoc sign unsigned code
+    bool wasAdhocSigned = false;
        if (rc == errSecCSUnsigned && handleUnsigned && (!overrideAssessment(flags) || SYSPOLICY_RECORDER_MODE_ENABLED())) {
                if (temporarySigning(code, type, path, 0)) {
+            wasAdhocSigned = true;
                        rc = errSecSuccess;             // clear unsigned; we are now well-signed
                        validationFlags |= kSecCSBasicValidateOnly;     // no need to re-validate deep contents
                }
@@ -343,6 +412,7 @@ void PolicyEngine::evaluateCode(CFURLRef path, AuthorityType type, SecAssessment
        
        // prepare for deep traversal of (hopefully) good signatures
        SecAssessmentFeedback feedback = SecAssessmentFeedback(CFDictionaryGetValue(context, kSecAssessmentContextKeyFeedback));
+       __block CFRef<CFMutableDictionaryRef> nestedFailure = NULL;     // save a nested failure for later
        MacOSError::check(SecStaticCodeSetCallback(code, kSecCSDefaultFlags, NULL, ^CFTypeRef (SecStaticCodeRef item, CFStringRef cfStage, CFDictionaryRef info) {
                string stage = cfString(cfStage);
                if (stage == "prepared") {
@@ -358,14 +428,24 @@ void PolicyEngine::evaluateCode(CFURLRef path, AuthorityType type, SecAssessment
                        SecStaticCodeSetCallback(item, kSecCSDefaultFlags, NULL, NULL);         // clear callback to avoid unwanted recursion
                        evaluateCodeItem(item, path, type, flags, item != code, result);
                        if (CFTypeRef verdict = CFDictionaryGetValue(result, kSecAssessmentAssessmentVerdict))
-                               if (CFEqual(verdict, kCFBooleanFalse))
-                                       return makeCFNumber(OSStatus(errSecCSVetoed));  // (signal nested-code policy failure, picked up below)
+                               if (CFEqual(verdict, kCFBooleanFalse)) {
+                                       if (item == code)
+                                               return makeCFNumber(OSStatus(errSecCSVetoed));  // (signal nested-code policy failure, picked up below)
+                                       // nested code policy failure; save, reset, and continue
+                                       if (!nestedFailure)
+                                               nestedFailure = CFMutableDictionaryRef(CFDictionaryGetValue(result, kSecAssessmentAssessmentAuthority));
+                                       CFDictionaryRemoveValue(result, kSecAssessmentAssessmentAuthority);
+                                       CFDictionaryRemoveValue(result, kSecAssessmentAssessmentVerdict);
+                               }
                }
                return NULL;
        }));
-       
+
        // go for it!
-       switch (rc = SecStaticCodeCheckValidity(code, validationFlags | kSecCSCheckNestedCode | kSecCSRestrictSymlinks | kSecCSReportProgress, NULL)) {
+       SecCSFlags topFlags = validationFlags | kSecCSCheckNestedCode | kSecCSRestrictSymlinks | kSecCSReportProgress;
+       if (type == kAuthorityExecute && !appOk)
+               topFlags |= kSecCSRestrictToAppLike;
+       switch (rc = SecStaticCodeCheckValidity(code, topFlags, NULL)) {
        case errSecSuccess:             // continue below
                break;
        case errSecCSUnsigned:
@@ -373,6 +453,8 @@ void PolicyEngine::evaluateCode(CFURLRef path, AuthorityType type, SecAssessment
                addAuthority(flags, result, "no usable signature");
                return;
        case errSecCSVetoed:            // nested code rejected by rule book; result was filled out there
+        if (wasAdhocSigned)
+            addToAuthority(result, kSecAssessmentAssessmentSource, CFSTR("no usable signature"));   // ad-hoc signature proved useless
                return;
        case errSecCSWeakResourceRules:
        case errSecCSWeakResourceEnvelope:
@@ -385,6 +467,7 @@ void PolicyEngine::evaluateCode(CFURLRef path, AuthorityType type, SecAssessment
        case errSecCSUnsealedAppRoot:
        case errSecCSUnsealedFrameworkRoot:
        case errSecCSInvalidSymlink:
+       case errSecCSNotAppLike:
        {
                // consult the whitelist
                bool allow = false;
@@ -397,8 +480,9 @@ void PolicyEngine::evaluateCode(CFURLRef path, AuthorityType type, SecAssessment
                        if (CFEqual(verdict, kCFBooleanFalse))  // nested code rejected by rule book; result was filled out there
                                return;
                        if (CFEqual(verdict, kCFBooleanTrue) && !(flags & kSecAssessmentFlagIgnoreWhitelist))
-                               if (mOpaqueWhitelist.contains(code, feedback, rc))
+                               if (opaqueWhiteListContains(code, feedback, rc)) {
                                        allow = true;
+                               }
                }
                if (allow) {
                        label = "allowed cdhash";
@@ -413,6 +497,29 @@ void PolicyEngine::evaluateCode(CFURLRef path, AuthorityType type, SecAssessment
        default:
                MacOSError::throwMe(rc);
        }
+
+       // Copy notarization date, if present, from code signing information
+       CFRef<CFDictionaryRef> info;
+       OSStatus status = SecCodeCopySigningInformation(code, kSecCSInternalInformation, &info.aref());
+       if (status == 0 && info) {
+               CFDateRef date = (CFDateRef)CFDictionaryGetValue(info, kSecCodeInfoNotarizationDate);
+               if (date) {
+                       cfadd(result, "{%O=%O}", kSecAssessmentAssessmentNotarizationDate, date);
+               }
+       } else {
+               secerror("Unable to copy signing information: %d", (int)status);
+       }
+       
+       if (nestedFailure && CFEqual(CFDictionaryGetValue(result, kSecAssessmentAssessmentVerdict), kCFBooleanTrue)) {
+               // structure intact, top level approved, nested code failed policy
+               CFMutableDictionaryRef authority = CFMutableDictionaryRef(CFDictionaryGetValue(result, kSecAssessmentAssessmentAuthority));
+               uint64_t ruleFlags = cfNumber<uint64_t>(CFNumberRef(CFDictionaryGetValue(authority, kSecAssessmentAssessmentAuthorityFlags)));
+               if (ruleFlags & kAuthorityFlagDefault) {
+                       // default rule requires positive match at each nested code - reinstate failure
+                       CFDictionaryReplaceValue(result, kSecAssessmentAssessmentVerdict, kCFBooleanFalse);
+                       CFDictionaryReplaceValue(result, kSecAssessmentAssessmentAuthority, nestedFailure);
+               }
+       }
 }
 
 
@@ -464,6 +571,9 @@ void PolicyEngine::evaluateInstall(CFURLRef path, SecAssessmentFlags flags, CFDi
        if (CFRef<CFArrayRef> certs = xar.copyCertChain()) {
                CFRef<CFTypeRef> policy = installerPolicy();
                CFRef<SecTrustRef> trust;
+               CFRef<CFDataRef> checksum;
+               CFRef<CFStringRef> teamID;
+               CFRef<CFMutableDictionaryRef> requirementContext = makeCFMutableDictionary();
                MacOSError::check(SecTrustCreateWithCertificates(certs, policy, &trust.aref()));
 //             MacOSError::check(SecTrustSetAnchorCertificates(trust, cfEmptyArray())); // no anchors
                MacOSError::check(SecTrustSetOptions(trust, kSecTrustOptionAllowExpired | kSecTrustOptionImplicitAnchors));
@@ -489,6 +599,42 @@ void PolicyEngine::evaluateInstall(CFURLRef path, SecAssessmentFlags flags, CFDi
                        }
                }
 
+               xar.registerStapledNotarization();
+               checksum.take(xar.createPackageChecksum());
+               if (checksum) {
+                       double notarizationDate = NAN;
+
+                       // Force a single online check for the checksum, which is always SHA1.
+                       bool is_revoked = checkNotarizationServiceForRevocation(checksum, kSecCodeSignatureHashSHA1, &notarizationDate);
+                       if (is_revoked) {
+                               MacOSError::throwMe(errSecCSRevokedNotarization);
+                       }
+
+                       // Extract a team identifier from the certificates.  This isn't validated and could be spoofed,
+                       // but since the 'legacy' keyword is only used in addition to the Developer ID requirement,
+                       // this is still stafe for now.
+                       SecCertificateRef leaf = SecCertificateRef(CFArrayGetValueAtIndex(certs, 0));
+                       CFRef<CFArrayRef> orgUnits = SecCertificateCopyOrganizationalUnit(leaf);
+                       if (orgUnits.get() && CFArrayGetCount(orgUnits) == 1) {
+                               teamID = (CFStringRef)CFArrayGetValueAtIndex(orgUnits, 0);
+                       }
+
+                       // Create the appropriate requirement context entry to allow notarized requirement check.
+                       CFRef<CFNumberRef> algorithm = makeCFNumber((uint32_t)xar.checksumDigestAlgorithm());
+                       cfadd(requirementContext, "{%O=%O}", kSecRequirementKeyPackageChecksum, checksum.get());
+                       cfadd(requirementContext, "{%O=%O}", kSecRequirementKeyChecksumAlgorithm, algorithm.get());
+                       if (teamID.get()) {
+                               cfadd(requirementContext, "{%O=%O}", kSecRequirementKeyTeamIdentifier, teamID.get());
+                       }
+
+                       if (!isnan(notarizationDate)) {
+                               CFRef<CFDateRef> date = CFDateCreate(NULL, notarizationDate);
+                               if (date) {
+                                       cfadd(result, "{%O=%O}", kSecAssessmentAssessmentNotarizationDate, date.get());
+                               }
+                       }
+               }
+
                SQLite::Statement query(*this,
                        "SELECT allow, requirement, id, label, flags, disabled FROM scan_authority"
                        " WHERE type = :type"
@@ -501,10 +647,10 @@ void PolicyEngine::evaluateInstall(CFURLRef path, SecAssessmentFlags flags, CFDi
                        const char *label = query[3];
                        //sqlite_uint64 ruleFlags = query[4];
                        SQLite3::int64 disabled = query[5];
-       
+
                        CFRef<SecRequirementRef> requirement;
                        MacOSError::check(SecRequirementCreateWithString(CFTempString(reqString), kSecCSDefaultFlags, &requirement.aref()));
-                       switch (OSStatus rc = SecRequirementEvaluate(requirement, chain, NULL, kSecCSDefaultFlags)) {
+                       switch (OSStatus rc = SecRequirementEvaluate(requirement, chain, requirementContext.get(), kSecCSDefaultFlags)) {
                        case errSecSuccess: // success
                                break;
                        case errSecCSReqFailed: // requirement missed, but otherwise okay
@@ -546,38 +692,17 @@ void PolicyEngine::evaluateInstall(CFURLRef path, SecAssessmentFlags flags, CFDi
 //
 // Create a suitable policy array for verification of installer signatures.
 //
-static SecPolicyRef makeCRLPolicy()
+static SecPolicyRef makeRevocationPolicy()
 {
-       CFRef<SecPolicyRef> policy;
-       MacOSError::check(SecPolicyCopy(CSSM_CERT_X_509v3, &CSSMOID_APPLE_TP_REVOCATION_CRL, &policy.aref()));
-       CSSM_APPLE_TP_CRL_OPTIONS options;
-       memset(&options, 0, sizeof(options));
-       options.Version = CSSM_APPLE_TP_CRL_OPTS_VERSION;
-       options.CrlFlags = CSSM_TP_ACTION_FETCH_CRL_FROM_NET | CSSM_TP_ACTION_CRL_SUFFICIENT;
-       CSSM_DATA optData = { sizeof(options), (uint8 *)&options };
-       MacOSError::check(SecPolicySetValue(policy, &optData));
-       return policy.yield();
-}
-
-static SecPolicyRef makeOCSPPolicy()
-{
-       CFRef<SecPolicyRef> policy;
-       MacOSError::check(SecPolicyCopy(CSSM_CERT_X_509v3, &CSSMOID_APPLE_TP_REVOCATION_OCSP, &policy.aref()));
-       CSSM_APPLE_TP_OCSP_OPTIONS options;
-       memset(&options, 0, sizeof(options));
-       options.Version = CSSM_APPLE_TP_OCSP_OPTS_VERSION;
-       options.Flags = CSSM_TP_ACTION_OCSP_SUFFICIENT;
-       CSSM_DATA optData = { sizeof(options), (uint8 *)&options };
-       MacOSError::check(SecPolicySetValue(policy, &optData));
+       CFRef<SecPolicyRef> policy(SecPolicyCreateRevocation(kSecRevocationUseAnyAvailableMethod));
        return policy.yield();
 }
 
 static CFTypeRef installerPolicy()
 {
        CFRef<SecPolicyRef> base = SecPolicyCreateBasicX509();
-       CFRef<SecPolicyRef> crl = makeCRLPolicy();
-       CFRef<SecPolicyRef> ocsp = makeOCSPPolicy();
-       return makeCFArray(3, base.get(), crl.get(), ocsp.get());
+       CFRef<SecPolicyRef> revoc = makeRevocationPolicy();
+       return makeCFArray(2, base.get(), revoc.get());
 }
 
 
@@ -588,8 +713,21 @@ static CFTypeRef installerPolicy()
 void PolicyEngine::evaluateDocOpen(CFURLRef path, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result)
 {
        if (context) {
+               FileQuarantine qtn(cfString(path).c_str());
+               if (CFDictionaryGetValue(context, kSecAssessmentContextKeyPrimarySignature) == kCFBooleanTrue) {
+                       // Client requests that we focus on the code signature on this document and report on that.
+                       // On this path, we care about the (code) signature on the document, not its risk assessment,
+                       // and any exception is reported as a primary error.
+                       if (qtn.flag(QTN_FLAG_ASSESSMENT_OK)) {
+                               // previously added by user - hacked to say no/no usable signature to trigger proper DMG processing in XProtect
+                               cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict);
+                addAuthority(flags, result, "no usable signature");
+                               return;
+                       }
+                       evaluateCode(path, kAuthorityOpenDoc, flags, context, result, true);
+                       return;
+               }
                if (CFStringRef riskCategory = CFStringRef(CFDictionaryGetValue(context, kLSDownloadRiskCategoryKey))) {
-                       FileQuarantine qtn(cfString(path).c_str());
 
                        if (CFEqual(riskCategory, kLSRiskCategorySafe)
                                || CFEqual(riskCategory, kLSRiskCategoryNeutral)
@@ -600,11 +738,12 @@ void PolicyEngine::evaluateDocOpen(CFURLRef path, SecAssessmentFlags flags, CFDi
                        } else if (qtn.flag(QTN_FLAG_HARD)) {
                                MacOSError::throwMe(errSecCSFileHardQuarantined);
                        } else if (qtn.flag(QTN_FLAG_ASSESSMENT_OK)) {
+                               // previously added by user
                                cfadd(result, "{%O=#T}", kSecAssessmentAssessmentVerdict);
                                addAuthority(flags, result, "Prior Assessment");
                        } else if (!overrideAssessment(flags)) {                // no need to do more work if we're off
                                try {
-                                       evaluateCode(path, kAuthorityExecute, flags, context, result, false);
+                                       evaluateCode(path, kAuthorityOpenDoc, flags, context, result, true);
                                } catch (...) {
                                        // some documents can't be code signed, so this may be quite benign
                                }
@@ -626,7 +765,7 @@ void PolicyEngine::evaluateDocOpen(CFURLRef path, SecAssessmentFlags flags, CFDi
 //
 // Result-creation helpers
 //
-void PolicyEngine::addAuthority(SecAssessmentFlags flags, CFMutableDictionaryRef parent, const char *label, SQLite::int64 row, CFTypeRef cacheInfo, bool weak)
+void PolicyEngine::addAuthority(SecAssessmentFlags flags, CFMutableDictionaryRef parent, const char *label, SQLite::int64 row, CFTypeRef cacheInfo, bool weak, uint64_t ruleFlags)
 {
        CFRef<CFMutableDictionaryRef> auth = makeCFMutableDictionary();
        if (label && label[0])
@@ -637,6 +776,7 @@ void PolicyEngine::addAuthority(SecAssessmentFlags flags, CFMutableDictionaryRef
                CFDictionaryAddValue(auth, kSecAssessmentAssessmentAuthorityOverride, kDisabledOverride);
        if (cacheInfo)
                CFDictionaryAddValue(auth, kSecAssessmentAssessmentFromCache, cacheInfo);
+       CFDictionaryAddValue(auth, kSecAssessmentAssessmentAuthorityFlags, CFTempNumber(ruleFlags));
        if (weak) {
                CFDictionaryAddValue(auth, kSecAssessmentAssessmentWeakSignature, kCFBooleanTrue);
                CFDictionaryReplaceValue(parent, kSecAssessmentAssessmentAuthority, auth);
@@ -715,7 +855,7 @@ CFDictionaryRef PolicyEngine::add(CFTypeRef inTarget, AuthorityType type, SecAss
        bool allow = true;
        double expires = never;
        string remarks;
-       SQLite::uint64 dbFlags = kAuthorityFlagWhitelistV2;
+       SQLite::uint64 dbFlags = kAuthorityFlagWhitelistV2 | kAuthorityFlagWhitelistSHA256;
        
        if (CFNumberRef pri = ctx.get<CFNumberRef>(kSecAssessmentUpdateKeyPriority))
                CFNumberGetValue(pri, kCFNumberDoubleType, &priority);
@@ -789,6 +929,9 @@ CFDictionaryRef PolicyEngine::disable(CFTypeRef target, AuthorityType type, SecA
 
 CFDictionaryRef PolicyEngine::find(CFTypeRef target, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context)
 {
+    //for privacy reasons we only want to allow the admin to list the database
+    authorizeUpdate(flags, context);
+
        SQLite::Statement query(*this);
        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",
                "scan_authority", target, type, flags, context,
@@ -1036,27 +1179,13 @@ void PolicyEngine::normalizeTarget(CFRef<CFTypeRef> &target, AuthorityType type,
                        }
                        break;
                case errSecCSUnsigned:
-                       if (signUnsigned && temporarySigning(code, type, path, kAuthorityFlagWhitelistV2)) {    // ad-hoc signed the code temporarily
+                       if (signUnsigned && temporarySigning(code, type, path, kAuthorityFlagWhitelistV2 | kAuthorityFlagWhitelistSHA256)) {    // ad-hoc sign the code temporarily
                                MacOSError::check(SecCodeCopyDesignatedRequirement(code, kSecCSDefaultFlags, (SecRequirementRef *)&target.aref()));
-                               CFRef<CFDictionaryRef> info;
-                               MacOSError::check(SecCodeCopySigningInformation(code, kSecCSInternalInformation, &info.aref()));
-                               if (CFDataRef cdData = CFDataRef(CFDictionaryGetValue(info, kSecCodeInfoCodeDirectory)))
-                                       *signUnsigned = ((const CodeDirectory *)CFDataGetBytePtr(cdData))->screeningCode();
+                               *signUnsigned = createWhitelistScreen(code);
                                break;
                        }
                        MacOSError::check(rc);
                case errSecCSSignatureFailed:
-                       // recover certain cases of broken signatures (well, try)
-                       if (codeInvalidityExceptions(code, NULL)) {
-                               // Ad-hoc sign the code in place (requiring a writable subject). This requires root privileges.
-                               CFRef<SecCodeSignerRef> signer;
-                               CFTemp<CFDictionaryRef> arguments("{%O=#N}", kSecCodeSignerIdentity);
-                               MacOSError::check(SecCodeSignerCreate(arguments, kSecCSSignOpaque, &signer.aref()));
-                               MacOSError::check(SecCodeSignerAddSignature(signer, code, kSecCSDefaultFlags));
-                               MacOSError::check(SecCodeCopyDesignatedRequirement(code, kSecCSDefaultFlags, (SecRequirementRef *)&target.aref()));
-                               break;
-                       }
-                       MacOSError::check(rc);
                default:
                        MacOSError::check(rc);
                }
@@ -1071,35 +1200,10 @@ void PolicyEngine::normalizeTarget(CFRef<CFTypeRef> &target, AuthorityType type,
                CFStringRef edit = CFStringRef(context.get(kSecAssessmentContextKeyUpdate));
                if (type == kAuthorityExecute && CFEqual(edit, kSecAssessmentUpdateOperationAdd)) {
                        // implicitly whitelist the code
-                       mOpaqueWhitelist.add(code);
+                       opaqueWhitelistAdd(code);
                }
        }
 }
 
-
-//
-// Process special overrides for invalidly signed code.
-// This is the (hopefully minimal) concessions we make to keep hurting our customers
-// for our own prior mistakes...
-//
-static bool codeInvalidityExceptions(SecStaticCodeRef code, CFMutableDictionaryRef result)
-{
-       if (OSAIsRecognizedExecutableURL) {
-               CFRef<CFDictionaryRef> info;
-               MacOSError::check(SecCodeCopySigningInformation(code, kSecCSDefaultFlags, &info.aref()));
-               if (CFURLRef executable = CFURLRef(CFDictionaryGetValue(info, kSecCodeInfoMainExecutable))) {
-                       SInt32 error;
-                       if (OSAIsRecognizedExecutableURL(executable, &error)) {
-                               if (result)
-                                       CFDictionaryAddValue(result,
-                                               kSecAssessmentAssessmentAuthorityOverride, CFSTR("ignoring known invalid applet signature"));
-                               return true;
-                       }
-               }
-       }
-       return false;
-}
-
-
 } // end namespace CodeSigning
 } // end namespace Security