]> git.saurik.com Git - apple/libsecurity_codesigning.git/commitdiff
libsecurity_codesigning-55037.6.tar.gz mac-os-x-1074 v55037.6
authorApple <opensource@apple.com>
Mon, 26 Mar 2012 18:47:55 +0000 (18:47 +0000)
committerApple <opensource@apple.com>
Mon, 26 Mar 2012 18:47:55 +0000 (18:47 +0000)
23 files changed:
lib/CSCommon.h
lib/SecAssessment.cpp
lib/SecAssessment.h
lib/SecCode.cpp
lib/SecStaticCode.cpp
lib/StaticCode.cpp
lib/StaticCode.h
lib/bundlediskrep.cpp
lib/codedirectory.cpp
lib/codedirectory.h
lib/policydb.cpp
lib/policydb.h
lib/policyengine.cpp
lib/policyengine.h
lib/quarantine++.cpp [new file with mode: 0644]
lib/quarantine++.h [new file with mode: 0644]
lib/reqdumper.cpp
lib/security_codesigning.exp
lib/syspolicy.sql
lib/xpcengine.cpp
lib/xpcengine.h
libsecurity_codesigning.xcodeproj/project.pbxproj
requirements.grammar

index 4aae1709b703dae3fdb0b5a72f1a1d98f9083b10..3c87b6213cadc8b2cbecb1a0c4b4ad61b9ca44a3 100644 (file)
@@ -89,6 +89,7 @@ enum {
        errSecCSInfoPlistFailed =                       -67030, /* invalid Info.plist (plist or signature have been modified) */
        errSecCSNoMainExecutable =                      -67029, /* the code has no main executable file */
        errSecCSBadBundleFormat =                       -67028, /* bundle format unrecognized, invalid, or unsuitable */
+       errSecCSNoMatches =                             -67027, /* no matches for search or update operation */
 };
 
 
@@ -176,6 +177,7 @@ enum {
        kSecCSDefaultFlags = 0,                                 /* no particular flags (default behavior) */
        
        kSecCSConsiderExpiration = 1 << 31,             /* consider expired certificates invalid */
+       kSecCSEnforceRevocationChecks = 1 << 30,        /* force revocation checks regardless of preference settings */
 };
 
 
index d5a5a89a5e2edf9fb4608e70961491942ab77b2f..fbecc413a049cf2edc5adee7e6d01fd99abf600e 100644 (file)
@@ -30,6 +30,7 @@
 #include <security_utilities/globalizer.h>
 #include <security_utilities/unix++.h>
 #include <security_utilities/cfmunge.h>
+#include <notify.h>
 
 using namespace CodeSigning;
 
@@ -114,42 +115,9 @@ ModuleNexus<ReadPolicy> gDatabase;
 ModuleNexus<PolicyEngine> gEngine;
 
 
-//
-// Help mapping API-ish CFString keys to more convenient internal enumerations
-//
-typedef struct {
-       CFStringRef cstring;
-       uint enumeration;
-} StringMap;
-
-static uint mapEnum(CFDictionaryRef context, CFStringRef attr, const StringMap *map, uint value = 0)
-{
-       if (context)
-               if (CFTypeRef value = CFDictionaryGetValue(context, attr))
-                       for (const StringMap *mp = map; mp->cstring; ++mp)
-                               if (CFEqual(mp->cstring, value))
-                                       return mp->enumeration;
-       if (value)
-               return value;
-       MacOSError::throwMe(errSecCSInvalidAttributeValues);
-}
-
-static const StringMap mapType[] = {
-       { kSecAssessmentOperationTypeExecute, kAuthorityExecute },
-       { kSecAssessmentOperationTypeInstall, kAuthorityInstall },
-       { kSecAssessmentOperationTypeOpenDocument, kAuthorityOpenDoc },
-       { NULL }
-};
-
-static AuthorityType typeFor(CFDictionaryRef context, AuthorityType type = kAuthorityInvalid)
-{ return mapEnum(context, kSecAssessmentContextKeyOperation, mapType, type); }
-
-
 //
 // Policy evaluation ("assessment") operations
 //
-const CFStringRef kSecAssessmentContextKeyCertificates = CFSTR("context:certificates");
-
 const CFStringRef kSecAssessmentAssessmentVerdict = CFSTR("assessment:verdict");
 const CFStringRef kSecAssessmentAssessmentOriginator = CFSTR("assessment:originator");
 const CFStringRef kSecAssessmentAssessmentAuthority = CFSTR("assessment:authority");
@@ -158,6 +126,8 @@ const CFStringRef kSecAssessmentAssessmentAuthorityRow = CFSTR("assessment:autho
 const CFStringRef kSecAssessmentAssessmentAuthorityOverride = CFSTR("assessment:authority:override");
 const CFStringRef kSecAssessmentAssessmentFromCache = CFSTR("assessment:authority:cached");
 
+const CFStringRef kSecAssessmentContextKeyCertificates = CFSTR("context:certificates");        // obsolete
+
 SecAssessmentRef SecAssessmentCreate(CFURLRef path,
        SecAssessmentFlags flags,
        CFDictionaryRef context,
@@ -189,10 +159,18 @@ SecAssessmentRef SecAssessmentCreate(CFURLRef path,
                        xpcEngineAssess(path, flags, context, result);
                }
        } catch (CommonError &error) {
-               if (!overrideAssessment())
-                       throw;          // let it go as an error
+               switch (error.osStatus()) {
+               case CSSMERR_TP_CERT_REVOKED:
+                       throw;
+               default:
+                       if (!overrideAssessment())
+                               throw;          // let it go as an error
+                       break;
+               }
+               // record the error we would have returned
                cfadd(result, "{%O=#F,'assessment:error'=%d}}", kSecAssessmentAssessmentVerdict, error.osStatus());
        } catch (...) {
+               // catch stray errors not conforming to the CommonError scheme
                if (!overrideAssessment())
                        throw;          // let it go as an error
                cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict);
@@ -289,16 +267,23 @@ CFDictionaryRef SecAssessmentCopyResult(SecAssessmentRef assessmentRef,
 
 
 //
-// Policy editing operations
+// Policy editing operations.
+// These all make permanent changes to the system-wide authority records.
 //
 const CFStringRef kSecAssessmentContextKeyUpdate = CFSTR("update");
-const CFStringRef kSecAssessmentUpdateOperationAddFile = CFSTR("update:addfile");
-const CFStringRef kSecAssessmentUpdateOperationRemoveFile = CFSTR("update:removefile");
+const CFStringRef kSecAssessmentUpdateOperationAdd = CFSTR("update:add");
+const CFStringRef kSecAssessmentUpdateOperationRemove = CFSTR("update:remove");
+const CFStringRef kSecAssessmentUpdateOperationEnable = CFSTR("update:enable");
+const CFStringRef kSecAssessmentUpdateOperationDisable = CFSTR("update:disable");
 
+const CFStringRef kSecAssessmentUpdateKeyAuthorization = CFSTR("update:authorization");
 const CFStringRef kSecAssessmentUpdateKeyPriority = CFSTR("update:priority");
 const CFStringRef kSecAssessmentUpdateKeyLabel = CFSTR("update:label");
+const CFStringRef kSecAssessmentUpdateKeyExpires = CFSTR("update:expires");
+const CFStringRef kSecAssessmentUpdateKeyAllow = CFSTR("update:allow");
+const CFStringRef kSecAssessmentUpdateKeyRemarks = CFSTR("update:remarks");
 
-Boolean SecAssessmentUpdate(CFURLRef path,
+Boolean SecAssessmentUpdate(CFTypeRef target,
        SecAssessmentFlags flags,
        CFDictionaryRef context,
        CFErrorRef *errors)
@@ -306,15 +291,14 @@ Boolean SecAssessmentUpdate(CFURLRef path,
        BEGIN_CSAPI
 
        CFDictionary ctx(context, errSecCSInvalidAttributeValues);
-       CFStringRef edit = ctx.get<CFStringRef>(kSecAssessmentContextKeyUpdate);
 
-       AuthorityType type = typeFor(context);
-       if (edit == kSecAssessmentUpdateOperationAddFile)
-               return gEngine().add(path, type, flags, context);
-       else if (edit == kSecAssessmentUpdateOperationRemoveFile)
-               MacOSError::throwMe(errSecCSUnimplemented);
-       else
-               MacOSError::throwMe(errSecCSInvalidAttributeValues);
+       if (flags & kSecAssessmentFlagDirect) {
+               // ask the engine right here to do its thing
+               return gEngine().update(target, flags, ctx);
+       } else {
+               // relay the question to our daemon for consideration
+               return xpcEngineUpdate(target, flags, ctx);
+       }
 
        END_CSAPI_ERRORS1(false)
 }
@@ -329,12 +313,14 @@ Boolean SecAssessmentControl(CFStringRef control, void *arguments, CFErrorRef *e
        BEGIN_CSAPI
        
        if (CFEqual(control, CFSTR("ui-enable"))) {
-               UnixPlusPlus::AutoFileDesc flagFile(visibleSecurityFlagFile, O_CREAT | O_WRONLY, 0644);
+               { UnixPlusPlus::AutoFileDesc flagFile(visibleSecurityFlagFile, O_CREAT | O_WRONLY, 0644); }
+               notify_post(kNotifySecAssessmentMasterSwitch);
                MessageTrace trace("com.apple.security.assessment.state", "enable");
                trace.send("enable assessment outcomes");
                return true;
        } else if (CFEqual(control, CFSTR("ui-disable"))) {
                if (::remove(visibleSecurityFlagFile) == 0 || errno == ENOENT) {
+                       notify_post(kNotifySecAssessmentMasterSwitch);
                        MessageTrace trace("com.apple.security.assessment.state", "disable");
                        trace.send("disable assessment outcomes");
                        return true;
@@ -347,6 +333,19 @@ Boolean SecAssessmentControl(CFStringRef control, void *arguments, CFErrorRef *e
                else
                        result = kCFBooleanTrue;
                return true;
+       } else if (CFEqual(control, CFSTR("ui-enable-devid"))) {
+               CFTemp<CFDictionaryRef> ctx("{%O=%s}", kSecAssessmentUpdateKeyLabel, "Developer ID");
+               return gEngine().enable(NULL, kAuthorityInvalid, kSecCSDefaultFlags, ctx);
+       } else if (CFEqual(control, CFSTR("ui-disable-devid"))) {
+               CFTemp<CFDictionaryRef> ctx("{%O=%s}", kSecAssessmentUpdateKeyLabel, "Developer ID");
+               return gEngine().disable(NULL, kAuthorityInvalid, kSecCSDefaultFlags, ctx);
+       } else if (CFEqual(control, CFSTR("ui-get-devid"))) {
+               CFBooleanRef &result = *(CFBooleanRef*)(arguments);
+               if (gEngine().value<int>("SELECT disabled FROM authority WHERE label = 'Developer ID';", true))
+                       result = kCFBooleanFalse;
+               else
+                       result = kCFBooleanTrue;
+               return true;
        } else
                MacOSError::throwMe(errSecCSInvalidAttributeValues);
 
index 546b139b6047a6430f29a93d8fd9e817c73360fc..9cae77543d25b23ecee66f71da7d51b7d6afc69e 100644 (file)
@@ -42,17 +42,27 @@ typedef struct _SecAssessment *SecAssessmentRef;
 CFTypeID SecAssessmentGetTypeID();
 
 
+/*
+ * Notifications sent when the policy authority database changes.
+ * (Should move to /usr/include/notify_keys.h eventually.)
+ */
+#define kNotifySecAssessmentMasterSwitch "com.apple.security.assessment.masterswitch"
+#define kNotifySecAssessmentUpdate "com.apple.security.assessment.update"
+
+
 /*!
- * Primary operation codes. These are operations the system policy can express
+ * Primary operation types. These are operations the system policy can express
  * opinions on. They are not operations *on* the system configuration itself.
  * (For those, see SecAssessmentUpdate below.)
  *
  * @constant kSecAssessmentContextKeyOperation Context key describing the type of operation
- *     being contemplated.
- * @constant kSecAssessmentOperationTypeInstall Value denoting the operation of installing
- *     software into the system.
+ *     being contemplated. The default varies depending on the API call used.
  * @constant kSecAssessmentOperationTypeExecute Value denoting the operation of running or executing
  *     code on the system.
+ * @constant kSecAssessmentOperationTypeInstall Value denoting the operation of installing
+ *     software into the system.
+ * @constant kSecAssessmentOperationTypeOpenDocument Value denoting the operation of opening
+ *     (in the LaunchServices sense) of documents.
  */
 extern const CFStringRef kSecAssessmentContextKeyOperation;    // proposed operation
 extern const CFStringRef kSecAssessmentOperationTypeExecute;   // .. execute code
@@ -71,6 +81,8 @@ extern const CFStringRef kSecAssessmentOperationTypeOpenDocument; // .. LaunchSe
                evaluation of system policy. This may be substantially slower.
        @constant kSecAssessmentFlagNoCache Do not save any evaluation outcome in the system caches.
                Any content already there is left undisturbed. Independent of kSecAssessmentFlagIgnoreCache.
+       @constant kSecAssessmentFlagEnforce Perform normal operations even if assessments have been
+               globally bypassed (which would usually approve anything).
        
        Flags common to multiple calls are assigned from high-bit down. Flags for particular calls
        are assigned low-bit up, and are documented with that call.
@@ -98,7 +110,7 @@ enum {
        @param context Optional CFDictionaryRef containing additional information bearing
                on the requested assessment.
        @param errors Standard CFError argument for reporting errors. Note that declining to permit
-               the proposed operation is not an error. Inability to form a judgment is.
+               the proposed operation is not an error. Inability to arrive at a judgment is.
        @result On success, a SecAssessment object that can be queried for its outcome.
                On error, NULL (with *errors set).
        
@@ -111,10 +123,6 @@ enum {
 
        @constant kSecAssessmentContextKeyOperation Type of operation (see overview above). This defaults
                to the kSecAssessmentOperationTypeExecute.
-       @constant kSecAssessmentContextKeyEdit A CFArray of SecCertificateRefs describing the
-               certificate chain of a CMS-type signature as pulled from 'path' by the caller.
-               The first certificate is the signing certificate. The certificates provided must be
-               sufficient to construct a valid certificate chain.
  */
 extern const CFStringRef kSecAssessmentContextKeyCertificates; // certificate chain as provided by caller
 
@@ -141,8 +149,6 @@ SecAssessmentRef SecAssessmentCreate(CFURLRef path,
 
        Extract results from a completed assessment and return them as a CFDictionary.
 
-       Assessment result keys (dictionary keys returned on success):
-
        @param assessment A SecAssessmentRef created with SecAssessmentCreate.
        @param flags Operation flags and options. Pass kSecAssessmentDefaultFlags for default
                behavior.
@@ -152,6 +158,8 @@ SecAssessmentRef SecAssessmentCreate(CFURLRef path,
                data as requested by flags. The caller owns this dictionary and should release it
                when done with it. On error, NULL (with *errors set).
 
+       Assessment result keys (dictionary keys returned on success):
+
        @constant kSecAssessmentAssessmentVerdict A CFBoolean value indicating whether the system policy
                allows (kCFBooleanTrue) or denies (kCFBooleanFalse) the proposed operation.
        @constant kSecAssessmentAssessmentAuthority A CFDictionary describing what sources of authority
@@ -169,9 +177,10 @@ CFDictionaryRef SecAssessmentCopyResult(SecAssessmentRef assessment,
        @function SecAssessmentUpdate
        Make changes to the system policy configuration.
        
-       @param path CFURL describing the file central to an operation - the program
-               to be executed, archive to be installed, plugin to be loaded, etc.
-               Pass NULL if the requested operation has no file subject.
+       @param path CFTypeRef describing the subject of the operation. Depending on the operation,
+               this may be a CFURL denoting a (single) file or bundle; a SecRequirement describing
+               a group of files; a CFNumber denoting an existing rule by rule number, or NULL to perform
+               global changes.
        @param flags Operation flags and options. Pass kSecAssessmentDefaultFlags for default
                behavior.
        @param context Required CFDictionaryRef containing information bearing
@@ -184,19 +193,42 @@ CFDictionaryRef SecAssessmentCopyResult(SecAssessmentRef assessment,
 
        @constant kSecAssessmentContextKeyEdit Required context key describing the kind of change
                requested to the system policy configuration. Currently understood values:
-       @constant kSecAssessmentUpdateOperationAddFile Request to add rules governing operations on the 'path'
-               argument.
-       @constant kSecAssessmentUpdateOperationRemoveFile Request to remove rules governing operations on the
-               'path' argument.
+       @constant kSecAssessmentUpdateOperationAdd Add a new rule to the assessment rule database.
+       @constant kSecAssessmentUpdateOperationRemove Remove rules from the rule database.
+       @constant kSecAssessmentUpdateOperationEnable (Re)enable rules in the rule database.
+       @constant kSecAssessmentUpdateOperationDisable Disable rules in the rule database.
+       @constant kSecAssessmentUpdateKeyAuthorization A CFData containing the external form of a
+               system AuthorizationRef used to authorize the change. The call will automatically generate
+               a suitable authorization if this is missing; however, if the request is on behalf of
+               another client, an AuthorizationRef should be created there and passed along here.
+       @constant kSecAssessmentUpdateKeyPriority CFNumber denoting a (floating point) priority
+               for the rule(s) being processed.
+       @constant kSecAssessmentUpdateKeyLabel CFString denoting a label string applied to the rule(s)
+               being processed.
+       @constant kSecAssessmentUpdateKeyExpires CFDate denoting an (absolute, future) expiration date
+               for rule(s) being processed.
+       @constant kSecAssessmentUpdateKeyAllow CFBoolean denoting whether a new rule allows or denies
+               assessment. The default is to allow; set to kCFBooleanFalse to create a negative (denial) rule.
+       @constant kSecAssessmentUpdateKeyRemarks CFString containing a colloquial description or comment
+               about a newly created rule. This is mean to be human readable and is not used when evaluating rules.
  */
 extern const CFStringRef kSecAssessmentContextKeyUpdate;               // proposed operation
-extern const CFStringRef kSecAssessmentUpdateOperationAddFile; // add to policy database
-extern const CFStringRef kSecAssessmentUpdateOperationRemoveFile; // remove from policy database
+extern const CFStringRef kSecAssessmentUpdateOperationAdd;             // add rule to policy database
+extern const CFStringRef kSecAssessmentUpdateOperationRemove;  // remove rule from policy database
+extern const CFStringRef kSecAssessmentUpdateOperationEnable;  // enable rule(s) in policy database
+extern const CFStringRef kSecAssessmentUpdateOperationDisable; // disable rule(s) in policy database
+
+extern const CFStringRef kSecAssessmentUpdateKeyAuthorization; // [CFData] external form of governing authorization
 
 extern const CFStringRef kSecAssessmentUpdateKeyPriority;              // rule priority
 extern const CFStringRef kSecAssessmentUpdateKeyLabel;                 // rule label
+extern const CFStringRef kSecAssessmentUpdateKeyExpires;               // rule expiration
+extern const CFStringRef kSecAssessmentUpdateKeyAllow;                 // rule outcome (allow/deny)
+extern const CFStringRef kSecAssessmentUpdateKeyRemarks;               // rule remarks (human readable)
 
-Boolean SecAssessmentUpdate(CFURLRef path,
+extern const CFStringRef kSecAssessmentContextKeyCertificates; // obsolete
+
+Boolean SecAssessmentUpdate(CFTypeRef target,
        SecAssessmentFlags flags,
        CFDictionaryRef context,
        CFErrorRef *errors);
@@ -204,7 +236,12 @@ Boolean SecAssessmentUpdate(CFURLRef path,
 
 /*!
        @function SecAssessmentControl
-       Miscellaneous system policy operations
+       Miscellaneous system policy operations.
+       
+       @param control A CFString indicating which operation is requested.
+       @param arguments Arguments to the operation as documented for control.
+       @param errors Standard CFErrorRef * argument to report errors.
+       @result Returns True on success. Returns False on failure (and sets *errors).
        
  */
 Boolean SecAssessmentControl(CFStringRef control, void *arguments, CFErrorRef *errors);
index fc64df445038f682626fb3c69ebbf177ee124932..779a7d309320ceb20046cbe9222a169a19c4e8b9 100644 (file)
@@ -197,7 +197,8 @@ OSStatus SecCodeCheckValidityWithErrors(SecCodeRef codeRef, SecCSFlags flags,
        BEGIN_CSAPI
        
        checkFlags(flags,
-               kSecCSConsiderExpiration);
+                 kSecCSConsiderExpiration
+               | kSecCSEnforceRevocationChecks);
        SecPointer<SecCode> code = SecCode::required(codeRef);
        code->checkValidity(flags);
        if (const SecRequirement *req = SecRequirement::optional(requirementRef))
index 3669928ecbf9364056f626be540395d5a0e1dd62..7cb5a17e6613030822852dc47fc6b098fdcf686e 100644 (file)
@@ -98,7 +98,7 @@ static void validateNested(string location, const SecRequirement *req, SecCSFlag
 static void validate(SecStaticCode *code, const SecRequirement *req, SecCSFlags flags)
 {
        try {
-               code->validateDirectory();
+               code->validateNonResourceComponents();  // also validates the CodeDirectory
                if (!(flags & kSecCSDoNotValidateExecutable))
                        code->validateExecutable();
                if (!(flags & kSecCSDoNotValidateResources))
@@ -187,6 +187,7 @@ OSStatus SecStaticCodeCheckValidityWithErrors(SecStaticCodeRef staticCodeRef, Se
                | kSecCSDoNotValidateExecutable
                | kSecCSDoNotValidateResources
                | kSecCSConsiderExpiration
+               | kSecCSEnforceRevocationChecks
                | kSecCSCheckNestedCode);
 
        SecPointer<SecStaticCode> code = SecStaticCode::requiredStatic(staticCodeRef);
index cec0b2b58d5e416ef9c2645288bb19f4b9ad1daf..956c88f9d53a70872baee63c676a79a3bcc1d290 100644 (file)
@@ -42,6 +42,7 @@
 #include <Security/SecCmsContentInfo.h>
 #include <Security/SecCmsSignerInfo.h>
 #include <Security/SecCmsSignedData.h>
+#include <Security/cssmapplePriv.h>
 #include <security_utilities/unix++.h>
 #include <security_utilities/cfmunge.h>
 #include <Security/CMSDecoder.h>
@@ -58,7 +59,7 @@ using namespace UnixPlusPlus;
 //
 SecStaticCode::SecStaticCode(DiskRep *rep)
        : mRep(rep),
-         mValidated(false), mExecutableValidated(false),
+         mValidated(false), mExecutableValidated(false), mResourcesValidated(false), mResourcesValidContext(NULL),
          mDesignatedReq(NULL), mGotResourceBase(false), mEvalDetails(NULL)
 {
        CODESIGN_STATIC_CREATE(this, rep);
@@ -72,6 +73,8 @@ SecStaticCode::SecStaticCode(DiskRep *rep)
 SecStaticCode::~SecStaticCode() throw()
 try {
        ::free(const_cast<Requirement *>(mDesignatedReq));
+       if (mResourcesValidContext)
+               delete mResourcesValidContext;
 } catch (...) {
        return;
 }
@@ -186,6 +189,11 @@ void SecStaticCode::resetValidity()
        CODESIGN_EVAL_STATIC_RESET(this);
        mValidated = false;
        mExecutableValidated = false;
+       mResourcesValidated = false;
+       if (mResourcesValidContext) {
+               delete mResourcesValidContext;
+               mResourcesValidContext = NULL;
+       }
        mDir = NULL;
        mSignature = NULL;
        for (unsigned n = 0; n < cdSlotCount; n++)
@@ -303,10 +311,7 @@ void SecStaticCode::validateDirectory()
                        CODESIGN_EVAL_STATIC_DIRECTORY(this);
                        mValidationExpired = verifySignature();
                        component(cdInfoSlot, errSecCSInfoPlistFailed); // force load of Info Dictionary (if any)
-                       CodeDirectory::SpecialSlot slot = codeDirectory()->nSpecialSlots;
-                       if (slot > cdSlotMax)   // might have more special slots than we know about...
-                               slot = cdSlotMax;       // ... but only process the ones we understand
-                       for ( ; slot >= 1; --slot)
+                       for (CodeDirectory::SpecialSlot slot = codeDirectory()->maxSpecialSlot(); slot >= 1; --slot)
                                if (mCache[slot])       // if we already loaded that resource...
                                        validateComponent(slot); // ... then check it now
                        mValidated = true;                      // we've done the deed...
@@ -332,6 +337,24 @@ void SecStaticCode::validateDirectory()
 }
 
 
+//
+// Load and validate the CodeDirectory and all components *except* those related to the resource envelope.
+// Those latter components are checked by validateResources().
+//
+void SecStaticCode::validateNonResourceComponents()
+{
+       this->validateDirectory();
+       for (CodeDirectory::SpecialSlot slot = codeDirectory()->maxSpecialSlot(); slot >= 1; --slot)
+               switch (slot) {
+               case cdResourceDirSlot:         // validated by validateResources
+                       break;
+               default:
+                       this->component(slot);          // loads and validates
+                       break;
+               }
+}
+
+
 //
 // Get the (signed) signing date from the code signature.
 // Sadly, we need to validate the signature to get the date (as a side benefit).
@@ -373,8 +396,9 @@ bool SecStaticCode::verifySignature()
        MacOSError::check(CMSDecoderSetDetachedContent(cms, mDir));
        MacOSError::check(CMSDecoderFinalizeMessage(cms));
        MacOSError::check(CMSDecoderSetSearchKeychain(cms, cfEmptyArray()));
+       CFRef<CFTypeRef> policy = verificationPolicy(apiFlags());
     CMSSignerStatus status;
-    MacOSError::check(CMSDecoderCopySignerStatus(cms, 0, verificationPolicy(),
+    MacOSError::check(CMSDecoderCopySignerStatus(cms, 0, policy,
                false, &status, &mTrust.aref(), NULL));
        if (status != kCMSSignerValid)
                MacOSError::throwMe(errSecCSSignatureFailed);
@@ -447,14 +471,47 @@ bool SecStaticCode::verifySignature()
 
 //
 // Return the TP policy used for signature verification.
-// This policy object is cached and reused.
+// This may be a simple SecPolicyRef or a CFArray of policies.
+// The caller owns the return value.
 //
-SecPolicyRef SecStaticCode::verificationPolicy()
+static SecPolicyRef makeCRLPolicy()
+{
+       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()
 {
-       if (!mPolicy)
-               MacOSError::check(SecPolicyCopy(CSSM_CERT_X_509v3,
-                       &CSSMOID_APPLE_TP_CODE_SIGNING, &mPolicy.aref()));
-       return mPolicy;
+       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));
+       return policy.yield();
+}
+
+CFTypeRef SecStaticCode::verificationPolicy(SecCSFlags flags)
+{
+       CFRef<SecPolicyRef> core;
+       MacOSError::check(SecPolicyCopy(CSSM_CERT_X_509v3,
+                       &CSSMOID_APPLE_TP_CODE_SIGNING, &core.aref()));
+       if (flags & kSecCSEnforceRevocationChecks) {
+               CFRef<SecPolicyRef> crl = makeCRLPolicy();
+               CFRef<SecPolicyRef> ocsp = makeOCSPPolicy();
+               return makeCFArray(3, core.get(), crl.get(), ocsp.get());
+       } else {
+               return core.yield();
+       }
 }
 
 
@@ -487,29 +544,43 @@ void SecStaticCode::validateComponent(CodeDirectory::SpecialSlot slot, OSStatus
 //
 void SecStaticCode::validateExecutable()
 {
-       DTRACK(CODESIGN_EVAL_STATIC_EXECUTABLE, this,
-               (char*)this->mainExecutablePath().c_str(), codeDirectory()->nCodeSlots);
-       const CodeDirectory *cd = this->codeDirectory();
-       if (!cd)
-               MacOSError::throwMe(errSecCSUnsigned);
-       AutoFileDesc fd(mainExecutablePath(), O_RDONLY);
-       fd.fcntl(F_NOCACHE, true);              // turn off page caching (one-pass)
-       if (Universal *fat = mRep->mainExecutableImage())
-               fd.seek(fat->archOffset());
-       size_t pageSize = cd->pageSize ? (1 << cd->pageSize) : 0;
-       size_t remaining = cd->codeLimit;
-       for (size_t slot = 0; slot < cd->nCodeSlots; ++slot) {
-               size_t size = min(remaining, pageSize);
-               if (!cd->validateSlot(fd, size, slot)) {
-                       CODESIGN_EVAL_STATIC_EXECUTABLE_FAIL(this, slot);
-                       mExecutableValidated = true;    // we tried
-                       mExecutableValid = false;               // it failed
-                       MacOSError::throwMe(errSecCSSignatureFailed);
+       if (!validatedExecutable()) {
+               try {
+                       DTRACK(CODESIGN_EVAL_STATIC_EXECUTABLE, this,
+                               (char*)this->mainExecutablePath().c_str(), codeDirectory()->nCodeSlots);
+                       const CodeDirectory *cd = this->codeDirectory();
+                       if (!cd) 
+                               MacOSError::throwMe(errSecCSUnsigned);
+                       AutoFileDesc fd(mainExecutablePath(), O_RDONLY);
+                       fd.fcntl(F_NOCACHE, true);              // turn off page caching (one-pass)
+                       if (Universal *fat = mRep->mainExecutableImage())
+                               fd.seek(fat->archOffset());
+                       size_t pageSize = cd->pageSize ? (1 << cd->pageSize) : 0;
+                       size_t remaining = cd->codeLimit;
+                       for (size_t slot = 0; slot < cd->nCodeSlots; ++slot) {
+                               size_t size = min(remaining, pageSize);
+                               if (!cd->validateSlot(fd, size, slot)) {
+                                       CODESIGN_EVAL_STATIC_EXECUTABLE_FAIL(this, slot);
+                                       MacOSError::throwMe(errSecCSSignatureFailed);
+                               }
+                               remaining -= size;
+                       }
+                       mExecutableValidated = true;
+                       mExecutableValidResult = noErr;
+               } catch (const CommonError &err) {
+                       mExecutableValidated = true;
+                       mExecutableValidResult = err.osStatus();
+                       throw;
+               } catch (...) {
+                       secdebug("staticCode", "%p executable validation threw non-common exception", this);
+                       mExecutableValidated = true;
+                       mExecutableValidResult = errSecCSInternalError;
+                       throw;
                }
-               remaining -= size;
        }
-       mExecutableValidated = true;    // we tried
-       mExecutableValid = true;                // it worked
+       assert(validatedExecutable());
+       if (mExecutableValidResult != noErr)
+               MacOSError::throwMe(mExecutableValidResult);
 }
 
 
@@ -522,48 +593,68 @@ void SecStaticCode::validateExecutable()
 //
 void SecStaticCode::validateResources()
 {
-       // sanity first
-       CFDictionaryRef sealedResources = resourceDictionary();
-       if (this->resourceBase())               // disk has resources
-               if (sealedResources)
-                       /* go to work below */;
-               else
-                       MacOSError::throwMe(errSecCSResourcesNotFound);
-       else                                                    // disk has no resources
-               if (sealedResources)
-                       MacOSError::throwMe(errSecCSResourcesNotFound);
-               else
-                       return;                                 // no resources, not sealed - fine (no work)
-
-       // found resources, and they are sealed
-       CFDictionaryRef rules = cfget<CFDictionaryRef>(sealedResources, "rules");
-       CFDictionaryRef files = cfget<CFDictionaryRef>(sealedResources, "files");
-       DTRACK(CODESIGN_EVAL_STATIC_RESOURCES, this,
-               (char*)this->mainExecutablePath().c_str(), int(CFDictionaryGetCount(files)));
-
-       // make a shallow copy of the ResourceDirectory so we can "check off" what we find
-       CFRef<CFMutableDictionaryRef> resourceMap = makeCFMutableDictionary(files);
-
-       // scan through the resources on disk, checking each against the resourceDirectory
-       CollectingContext ctx(*this);           // collect all failures in here
-       ResourceBuilder resources(cfString(this->resourceBase()), rules, codeDirectory()->hashType);
-       mRep->adjustResources(resources);
-       string path;
-       ResourceBuilder::Rule *rule;
-
-       while (resources.next(path, rule)) {
-               validateResource(path, ctx);
-               CFDictionaryRemoveValue(resourceMap, CFTempString(path));
-       }
-       
-       if (CFDictionaryGetCount(resourceMap) > 0) {
-               secdebug("staticCode", "%p sealed resource(s) not found in code", this);
-               CFDictionaryApplyFunction(resourceMap, SecStaticCode::checkOptionalResource, &ctx);
+       if (!validatedResources()) {
+               try {
+                       // sanity first
+                       CFDictionaryRef sealedResources = resourceDictionary();
+                       if (this->resourceBase())               // disk has resources
+                               if (sealedResources)
+                                       /* go to work below */;
+                               else
+                                       MacOSError::throwMe(errSecCSResourcesNotFound);
+                       else                                                    // disk has no resources
+                               if (sealedResources)
+                                       MacOSError::throwMe(errSecCSResourcesNotFound);
+                               else
+                                       return;                                 // no resources, not sealed - fine (no work)
+               
+                       // found resources, and they are sealed
+                       CFDictionaryRef rules = cfget<CFDictionaryRef>(sealedResources, "rules");
+                       CFDictionaryRef files = cfget<CFDictionaryRef>(sealedResources, "files");
+                       DTRACK(CODESIGN_EVAL_STATIC_RESOURCES, this,
+                               (char*)this->mainExecutablePath().c_str(), int(CFDictionaryGetCount(files)));
+               
+                       // make a shallow copy of the ResourceDirectory so we can "check off" what we find
+                       CFRef<CFMutableDictionaryRef> resourceMap = makeCFMutableDictionary(files);
+               
+                       // scan through the resources on disk, checking each against the resourceDirectory
+                       mResourcesValidContext = new CollectingContext(*this);          // collect all failures in here
+                       ResourceBuilder resources(cfString(this->resourceBase()), rules, codeDirectory()->hashType);
+                       mRep->adjustResources(resources);
+                       string path;
+                       ResourceBuilder::Rule *rule;
+               
+                       while (resources.next(path, rule)) {
+                               validateResource(path, *mResourcesValidContext);
+                               CFDictionaryRemoveValue(resourceMap, CFTempString(path));
+                       }
+                       
+                       if (CFDictionaryGetCount(resourceMap) > 0) {
+                               secdebug("staticCode", "%p sealed resource(s) not found in code", this);
+                               CFDictionaryApplyFunction(resourceMap, SecStaticCode::checkOptionalResource, mResourcesValidContext);
+                       }
+                       
+                       // now check for any errors found in the reporting context
+                       mResourcesValidated = true;
+                       if (mResourcesValidContext->osStatus() != noErr)
+                               mResourcesValidContext->throwMe();
+
+               } catch (const CommonError &err) {
+                       mResourcesValidated = true;
+                       mResourcesValidResult = err.osStatus();
+                       throw;
+               } catch (...) {
+                       secdebug("staticCode", "%p executable validation threw non-common exception", this);
+                       mResourcesValidated = true;
+                       mResourcesValidResult = errSecCSInternalError;
+                       throw;
+               }
        }
-       
-       // now check for any errors found in the reporting context
-       if (ctx)
-               ctx.throwMe();
+       assert(!validatedResources());
+       if (mResourcesValidResult)
+               MacOSError::throwMe(mResourcesValidResult);
+       if (mResourcesValidContext->osStatus() != noErr)
+               mResourcesValidContext->throwMe();
 }
 
 
@@ -1149,7 +1240,7 @@ void SecStaticCode::CollectingContext::reportProblem(OSStatus rc, CFStringRef ty
 void SecStaticCode::CollectingContext::throwMe()
 {
        assert(mStatus != noErr);
-       throw CSError(mStatus, mCollection.yield());
+       throw CSError(mStatus, mCollection.retain());
 }
 
 
index da616c82b797b6daf0e7d1c69e43c8b2ca65c75a..edb92a18b65dabdb919b088f201691fa6affce53 100644 (file)
@@ -80,6 +80,7 @@ protected:
                CollectingContext(SecStaticCode &c) : code(c), mStatus(noErr) { }
                void reportProblem(OSStatus rc, CFStringRef type, CFTypeRef value);
                
+               OSStatus osStatus()             { return mStatus; }
                operator OSStatus () const              { return mStatus; }
                void throwMe() __attribute__((noreturn));
                
@@ -135,9 +136,13 @@ public:
        bool validated() const  { return mValidated; }
        bool valid() const
                { assert(validated()); return mValidated && (mValidationResult == noErr); }
+       bool validatedExecutable() const        { return mExecutableValidated; }
+       bool validatedResources() const { return mResourcesValidated; }
+
        
        void validateDirectory();
        void validateComponent(CodeDirectory::SpecialSlot slot, OSStatus fail = errSecCSSignatureFailed);
+       void validateNonResourceComponents();
        void validateResources();
        void validateExecutable();
        
@@ -163,7 +168,7 @@ public:
 protected:
        CFDictionaryRef getDictionary(CodeDirectory::SpecialSlot slot, OSStatus fail); // component value as a dictionary
        bool verifySignature();
-       SecPolicyRef verificationPolicy();
+       CFTypeRef verificationPolicy(SecCSFlags flags);
 
        void defaultDesignatedAppleAnchor(Requirement::Maker &maker);
        void defaultDesignatedNonAppleAnchor(Requirement::Maker &maker);
@@ -182,7 +187,12 @@ private:
        
        // static executable validation state (nested within mValidated/mValid)
        bool mExecutableValidated;                      // tried to validate executable file
-       bool mExecutableValid;                          // outcome if mExecutableValidated
+       OSStatus mExecutableValidResult;                // outcome if mExecutableValidated
+
+       // static resource validation state (nested within mValidated/mValid)
+       bool mResourcesValidated;                       // tried to validate resources
+       OSStatus mResourcesValidResult;                 // outcome if mResourceValidated or..
+       CollectingContext *mResourcesValidContext;      // other outcome
 
        // cached contents
        CFRef<CFDataRef> mDir;                          // code directory data
@@ -204,9 +214,6 @@ private:
        CFRef<SecTrustRef> mTrust;                      // outcome of crypto validation (valid or not)
        CFRef<CFArrayRef> mCertChain;
        CSSM_TP_APPLE_EVIDENCE_INFO *mEvalDetails;
-       
-       // cached verification policy
-       CFRef<SecPolicyRef> mPolicy;
 };
 
 
index 757bbbc25d32237ec4a4f1bc72b425ef6776d173..09d5ca22fed9725e52892ea99b57b642849e81cb 100644 (file)
@@ -70,7 +70,7 @@ void BundleDiskRep::setup(const Context *ctx)
                if (ctx && ctx->version)        // explicitly specified
                        MacOSError::throwMe(errSecCSStaticCodeNotFound);
        }
-       
+
        // conventional executable bundle: CFBundle identifies an executable for us
        if (mMainExecutableURL.take(CFBundleCopyExecutableURL(mBundle))) {
                // conventional executable bundle
@@ -100,6 +100,7 @@ void BundleDiskRep::setup(const Context *ctx)
        CFRef<CFURLRef> infoURL = _CFBundleCopyInfoPlistURL(mBundle);
        if (!infoURL)
                MacOSError::throwMe(errSecCSBadBundleFormat);
+       
 
        // focus on the Info.plist (which we know exists) as the nominal "main executable" file
        if ((mMainExecutableURL = _CFBundleCopyInfoPlistURL(mBundle))) {
index 3eedb3c784ddc82b91fef39656844161f0fac4a6..9596e8f3f6578a296ce7747370e432d7c431d242 100644 (file)
@@ -35,6 +35,18 @@ namespace Security {
 namespace CodeSigning {
 
 
+//
+// Highest understood special slot in this CodeDirectory.
+//
+CodeDirectory::SpecialSlot CodeDirectory::maxSpecialSlot() const
+{
+       SpecialSlot slot = this->nSpecialSlots;
+       if (slot > cdSlotMax)
+               slot = cdSlotMax;
+       return slot;
+}
+
+
 //
 // Canonical filesystem names for select slot numbers.
 // These are variously used for filenames, extended attribute names, etc.
index b6f8b0ede4b584412fe97cc1a8835a799083936e..1028fd7f7d86f3300715888de5fd713353300619 100644 (file)
@@ -198,6 +198,8 @@ public:
        char *identifier() { return at<char>(identOffset); }
 
        // main hash array access
+       SpecialSlot maxSpecialSlot() const;
+               
        unsigned char *operator [] (Slot slot)
        {
                assert(slot >= int(-nSpecialSlots) && slot < int(nCodeSlots));
index 72018c3c91955d05d400910f43f85490e2abde76..b748dea1af5bb164fa485336a288589bb131efc4 100644 (file)
@@ -52,6 +52,37 @@ static const char *dbPath()
 }
 
 
+//
+// Help mapping API-ish CFString keys to more convenient internal enumerations
+//
+typedef struct {
+       const CFStringRef &cstring;
+       uint enumeration;
+} StringMap;
+
+static uint mapEnum(CFDictionaryRef context, CFStringRef attr, const StringMap *map, uint value = 0)
+{
+       if (context)
+               if (CFTypeRef value = CFDictionaryGetValue(context, attr))
+                       for (const StringMap *mp = map; mp->cstring; ++mp)
+                               if (CFEqual(mp->cstring, value))
+                                       return mp->enumeration;
+       return value;
+}
+
+static const StringMap mapType[] = {
+       { kSecAssessmentOperationTypeExecute, kAuthorityExecute },
+       { kSecAssessmentOperationTypeInstall, kAuthorityInstall },
+       { kSecAssessmentOperationTypeOpenDocument, kAuthorityOpenDoc },
+       { NULL }
+};
+
+AuthorityType typeFor(CFDictionaryRef context, AuthorityType type /* = kAuthorityInvalid */)
+{
+       return mapEnum(context, kSecAssessmentContextKeyOperation, mapType, type);
+}
+
+
 //
 // Open the database (creating it if necessary and possible).
 // Note that this isn't creating the schema; we do that on first write.
@@ -75,48 +106,64 @@ bool PolicyDatabase::checkCache(CFURLRef path, AuthorityType type, CFMutableDict
        if (type != kAuthorityExecute)
                return false;
        
-       SecCSFlags validationFlags = kSecCSDefaultFlags;
-       if (overrideAssessment())       // we'll force the verdict to 'pass' at the end, so don't sweat validating code
-               validationFlags = kSecCSBasicValidateOnly;
-
        CFRef<SecStaticCodeRef> code;
        MacOSError::check(SecStaticCodeCreateWithPath(path, kSecCSDefaultFlags, &code.aref()));
-       if (SecStaticCodeCheckValidity(code, validationFlags, NULL) != noErr)
+       if (SecStaticCodeCheckValidity(code, kSecCSBasicValidateOnly, NULL) != noErr)
                return false;   // quick pass - any error is a cache miss
        CFRef<CFDictionaryRef> info;
        MacOSError::check(SecCodeCopySigningInformation(code, kSecCSDefaultFlags, &info.aref()));
        CFDataRef cdHash = CFDataRef(CFDictionaryGetValue(info, kSecCodeInfoUnique));
        
        // check the cache table for a fast match
-       SQLite::Statement cached(*this, "SELECT allow, expires, label, authority FROM object WHERE type = ?1 and hash = ?2;");
-       cached.bind(1).integer(type);
-       cached.bind(2) = cdHash;
+       SQLite::Statement cached(*this, "SELECT object.allow, authority.label, authority FROM object, authority"
+               " WHERE object.authority = authority.id AND object.type = :type AND object.hash = :hash AND authority.disabled = 0"
+               " AND JULIANDAY('now') < object.expires;");
+       cached.bind(":type").integer(type);
+       cached.bind(":hash") = cdHash;
        if (cached.nextRow()) {
                bool allow = int(cached[0]);
-               const char *label = cached[2];
-               SQLite::int64 auth = cached[3];
-               bool valid = true;
-               if (SQLite3::int64 expires = cached[1])
-                       valid = time(NULL) <= expires;
-               if (valid) {
-                       SYSPOLICY_ASSESS_CACHE_HIT();
-                       cfadd(result, "{%O=%B}", kSecAssessmentAssessmentVerdict, allow);
-                       PolicyEngine::addAuthority(result, label, auth, kCFBooleanTrue);
-                       return true;
-               }
+               const char *label = cached[1];
+               SQLite::int64 auth = cached[2];
+               SYSPOLICY_ASSESS_CACHE_HIT();
+
+               // If its allowed, lets do a full validation unless if
+               // we are overriding the assessement, since that force
+               // the verdict to 'pass' at the end
+
+               if (allow && !overrideAssessment())
+                   MacOSError::check(SecStaticCodeCheckValidity(code, kSecCSDefaultFlags, NULL));
+
+               cfadd(result, "{%O=%B}", kSecAssessmentAssessmentVerdict, allow);
+               PolicyEngine::addAuthority(result, label, auth, kCFBooleanTrue);
+               return true;
        }
        return false;
 }
 
 
 //
-// Purge the object cache of all expired entries
+// Purge the object cache of all expired entries.
+// These are meant to run within the caller's transaction.
 //
-void PolicyDatabase::purge(const char *table)
+void PolicyDatabase::purgeAuthority()
+{
+       SQLite::Statement cleaner(*this,
+               "DELETE FROM authority WHERE expires <= JULIANDAY('now');");
+       cleaner.execute();
+}
+
+void PolicyDatabase::purgeObjects()
+{
+       SQLite::Statement cleaner(*this,
+               "DELETE FROM object WHERE expires <= JULIANDAY('now');");
+       cleaner.execute();
+}
+
+void PolicyDatabase::purgeObjects(double priority)
 {
        SQLite::Statement cleaner(*this,
-               "DELETE FROM ?1 WHERE expires < DATE_TIME('now');");
-       cleaner.bind(1) = table;
+               "DELETE FROM object WHERE expires <= JULIANDAY('now') OR (SELECT priority FROM authority WHERE id = object.authority) <= :priority;");
+       cleaner.bind(":priority") = priority;
        cleaner.execute();
 }
 
index 1b06b4a5e6ade3c07fde10f8186c47ace3ce9859..a112c16e3944c79d47b9c92af45911ae28db7f75 100644 (file)
@@ -38,6 +38,8 @@ namespace SQLite = SQLite3;
 static const char defaultDatabase[] = "/var/db/SystemPolicy";
 static const char visibleSecurityFlagFile[] = "/var/db/.sp_visible";
 
+static const double never = 5000000;   // expires never (i.e. in the year 8977)
+
 
 typedef SHA1::SDigest ObjectHash;
 
@@ -51,6 +53,22 @@ enum {
 };
 
 
+//
+// Defined flags for authority flags column
+//
+enum {
+       kAuthorityFlagVirtual = 0x0001, // virtual rule (anchoring object records)
+       kAuthorityFlagDefault = 0x0002, // rule is part of the original default set
+       kAuthorityFlagInhibitCache = 0x0004, // never cache outcome of this rule
+};
+
+
+//
+// Mapping/translation to/from API space
+//
+AuthorityType typeFor(CFDictionaryRef context, AuthorityType type = kAuthorityInvalid);
+
+
 //
 // An open policy database.
 // Usually read-only, but can be opened for write by privileged callers.
@@ -66,7 +84,9 @@ public:
        bool checkCache(CFURLRef path, AuthorityType type, CFMutableDictionaryRef result);
 
 public:
-       void purge(const char *table);
+       void purgeAuthority();
+       void purgeObjects();
+       void purgeObjects(double priority);
 };
 
 
index 99e087b3862cc6c0071152dda828fcd999feb28a..4ebe2a029d131170d8f1e3ed17fdf843f56b5cd2 100644 (file)
 #include <Security/Security.h>
 #include <Security/SecRequirementPriv.h>
 #include <Security/SecPolicyPriv.h>
+#include <Security/SecTrustPriv.h>
+#include <Security/SecCodeSigner.h>
+#include <Security/cssmapplePriv.h>
+#include <notify.h>
+#include <security_utilities/unix++.h>
+#include "quarantine++.h"
 
 #include <CoreServices/CoreServicesPriv.h>
 #undef check // Macro! Yech.
 namespace Security {
 namespace CodeSigning {
 
-static const time_t NEGATIVE_HOLD = 60;        // seconds for negative cache entries
+static const double NEGATIVE_HOLD = 60.0/86400;        // 60 seconds to cache negative outcomes
+
+
+static void authorizeUpdate(SecCSFlags flags, CFDictionaryRef context);
+static void normalizeTarget(CFRef<CFTypeRef> &target, CFDictionary &context, bool signUnsigned = false);
 
 
 //
@@ -81,64 +91,88 @@ void PolicyEngine::evaluateCode(CFURLRef path, SecAssessmentFlags flags, CFDicti
        CFRef<SecStaticCodeRef> code;
        MacOSError::check(SecStaticCodeCreateWithPath(path, kSecCSDefaultFlags, &code.aref()));
        
-       if (flags & kSecAssessmentFlagRequestOrigin) {
-               CFRef<CFDictionaryRef> info;
-               MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref()));
-               if (CFArrayRef chain = CFArrayRef(CFDictionaryGetValue(info, kSecCodeInfoCertificates)))
-                       setOrigin(chain, result);
-       }
-       
-       SecCSFlags validationFlags = kSecCSDefaultFlags;
-       if (overrideAssessment())       // we'll force the verdict to 'pass' at the end, so don't sweat validating code
-               validationFlags = kSecCSBasicValidateOnly;
+       const SecCSFlags validationFlags = kSecCSEnforceRevocationChecks;
 
        SQLite::Statement query(*this,
-               "SELECT allow, requirement, inhibit_cache, expires, id, label FROM authority WHERE type = ?1 ORDER BY priority DESC;");
-       query.bind(1).integer(type);
+               "SELECT allow, requirement, id, label, expires, flags, disabled FROM scan_authority"
+               " WHERE type = :type"
+               " ORDER BY priority DESC;");
+       query.bind(":type").integer(type);
+       SQLite3::int64 latentID = 0;            // first (highest priority) disabled matching ID
+       std::string latentLabel;                        // ... and associated label, if any
        while (query.nextRow()) {
                bool allow = int(query[0]);
                const char *reqString = query[1];
-               bool inhibit_cache = query[2];
-               time_t expires = SQLite::int64(query[3]);
-               SQLite3::int64 id = query[4];
-               const char *label = query[5];
-               
-               if (expires && expires < time(NULL))    // no longer active
-                       continue;
+               SQLite3::int64 id = query[2];
+               const char *label = query[3];
+               double expires = query[4];
+               sqlite3_int64 ruleFlags = query[5];
+               SQLite3::int64 disabled = query[6];
                
                CFRef<SecRequirementRef> requirement;
                MacOSError::check(SecRequirementCreateWithString(CFTempString(reqString), kSecCSDefaultFlags, &requirement.aref()));
-               switch (OSStatus rc = SecStaticCodeCheckValidity(code, validationFlags, requirement)) {
+
+               OSStatus rc = SecStaticCodeCheckValidity(code, validationFlags | kSecCSBasicValidateOnly, requirement);
+               // ok, so this rule matches lets do a full validation if not overriding assessments
+               if (rc == noErr && !overrideAssessment())
+                       rc = SecStaticCodeCheckValidity(code, validationFlags, requirement);
+
+               switch (rc) {
                case noErr: // success
                        break;
                case errSecCSUnsigned:
                        cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict);
                        addAuthority(result, "no usable signature");
                        return;
-               case errSecCSSignatureFailed:
-               case errSecCSSignatureInvalid:
-               case errSecCSSignatureUnsupported:
-               case errSecCSSignatureNotVerifiable:
-                       cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict);
-                       addAuthority(result, "invalid signature");
-                       return;
                case errSecCSReqFailed: // requirement missed, but otherwise okay
                        continue;
                default: // broken in some way; all tests will fail like this so bail out
                        MacOSError::throwMe(rc);
                }
-               if (!inhibit_cache && !(flags & kSecAssessmentFlagNoCache))     // cache inhibit
-                               this->recordOutcome(code, allow, type, expires, id, label);
+               if (disabled) {
+                       if (latentID == 0) {
+                               latentID = id;
+                               if (label)
+                                       latentLabel = label;
+                       }
+                       continue;       // the loop
+               }
+       
+               CFRef<CFDictionaryRef> info;    // as needed
+               if (flags & kSecAssessmentFlagRequestOrigin) {
+                       if (!info)
+                               MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref()));
+                       if (CFArrayRef chain = CFArrayRef(CFDictionaryGetValue(info, kSecCodeInfoCertificates)))
+                               setOrigin(chain, result);
+               }
+               if (!(ruleFlags & kAuthorityFlagInhibitCache) && !(flags & kSecAssessmentFlagNoCache)) {        // cache inhibit
+                       if (!info)
+                               MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref()));
+                       if (SecTrustRef trust = SecTrustRef(CFDictionaryGetValue(info, kSecCodeInfoTrust))) {
+                               CFRef<CFDictionaryRef> xinfo;
+                               MacOSError::check(SecTrustCopyExtendedResult(trust, &xinfo.aref()));
+                               if (CFDateRef limit = CFDateRef(CFDictionaryGetValue(xinfo, kSecTrustExpirationDate))) {
+                                       double julianLimit = CFDateGetAbsoluteTime(limit) / 86400.0 + 2451910.5;
+                                       this->recordOutcome(code, allow, type, min(expires, julianLimit), id);
+                               }
+                       }
+               }
                cfadd(result, "{%O=%B}", kSecAssessmentAssessmentVerdict, allow);
                addAuthority(result, label, id);
                return;
        }
        
        // no applicable authority. Deny by default
+       if (flags & kSecAssessmentFlagRequestOrigin) {
+               CFRef<CFDictionaryRef> info;    // as needed
+               MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref()));
+               if (CFArrayRef chain = CFArrayRef(CFDictionaryGetValue(info, kSecCodeInfoCertificates)))
+                       setOrigin(chain, result);
+       }
        if (!(flags & kSecAssessmentFlagNoCache))
-               this->recordOutcome(code, false, type, time(NULL) + NEGATIVE_HOLD, 0, NULL);
+               this->recordOutcome(code, false, type, this->julianNow() + NEGATIVE_HOLD, latentID);
        cfadd(result, "{%O=%B}", kSecAssessmentAssessmentVerdict, false);
-       addAuthority(result, NULL);
+       addAuthority(result, latentLabel.c_str(), latentID);
 }
 
 
@@ -147,10 +181,14 @@ void PolicyEngine::evaluateCode(CFURLRef path, SecAssessmentFlags flags, CFDicti
 // Certs passed from caller (untrusted), no policy engine yet, no caching (since untrusted).
 // The current "policy" is to trust any proper signature.
 //
+static CFTypeRef installerPolicy();
+
 void PolicyEngine::evaluateInstall(CFURLRef path, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result)
 {
        const AuthorityType type = kAuthorityInstall;
        
+       SQLite3::int64 latentID = 0;            // first (highest priority) disabled matching ID
+       std::string latentLabel;                        // ... and associated label, if any
        Xar xar(cfString(path).c_str());
        if (xar) {
                if (!xar.isSigned()) {
@@ -160,11 +198,11 @@ void PolicyEngine::evaluateInstall(CFURLRef path, SecAssessmentFlags flags, CFDi
                        return;
                }
                if (CFRef<CFArrayRef> certs = xar.copyCertChain()) {
-                       CFRef<SecPolicyRef> policy = SecPolicyCreateBasicX509();
+                       CFRef<CFTypeRef> policy = installerPolicy();
                        CFRef<SecTrustRef> trust;
                        MacOSError::check(SecTrustCreateWithCertificates(certs, policy, &trust.aref()));
 //                     MacOSError::check(SecTrustSetAnchorCertificates(trust, cfEmptyArray())); // no anchors
-                       MacOSError::check(SecTrustSetOptions(trust, kSecTrustOptionImplicitAnchors));
+                       MacOSError::check(SecTrustSetOptions(trust, kSecTrustOptionAllowExpired | kSecTrustOptionImplicitAnchors));
 
                        SecTrustResultType trustResult;
                        MacOSError::check(SecTrustEvaluate(trust, &trustResult));
@@ -189,18 +227,17 @@ void PolicyEngine::evaluateInstall(CFURLRef path, SecAssessmentFlags flags, CFDi
                        }
 
                        SQLite::Statement query(*this,
-                               "SELECT allow, requirement, inhibit_cache, expires, id, label FROM authority WHERE type = ?1 ORDER BY priority DESC;");
-                       query.bind(1).integer(type);
+                               "SELECT allow, requirement, id, label, flags, disabled FROM scan_authority"
+                               " WHERE type = :type"
+                               " ORDER BY priority DESC;");
+                       query.bind(":type").integer(type);
                        while (query.nextRow()) {
                                bool allow = int(query[0]);
                                const char *reqString = query[1];
-                               bool inhibit_cache = query[2];
-                               time_t expires = SQLite::int64(query[3]);
-                               SQLite3::int64 id = query[4];
-                               const char *label = query[5];
-                               
-                               if (expires && expires < time(NULL))    // no longer active
-                                       continue;
+                               SQLite3::int64 id = query[2];
+                               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()));
@@ -212,6 +249,14 @@ void PolicyEngine::evaluateInstall(CFURLRef path, SecAssessmentFlags flags, CFDi
                                default: // broken in some way; all tests will fail like this so bail out
                                        MacOSError::throwMe(rc);
                                }
+                               if (disabled) {
+                                       if (latentID == 0) {
+                                               latentID = id;
+                                               if (label)
+                                                       latentLabel = label;
+                                       }
+                                       continue;       // the loop
+                               }
                                // not adding to the object cache - we could, but it's not likely to be worth it
                                cfadd(result, "{%O=%B}", kSecAssessmentAssessmentVerdict, allow);
                                addAuthority(result, label, id);
@@ -222,7 +267,45 @@ void PolicyEngine::evaluateInstall(CFURLRef path, SecAssessmentFlags flags, CFDi
        
        // no applicable authority. Deny by default
        cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict);
-       addAuthority(result, NULL);
+       addAuthority(result, latentLabel.c_str(), latentID);
+}
+
+
+//
+// Create a suitable policy array for verification of installer signatures.
+//
+static SecPolicyRef makeCRLPolicy()
+{
+       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));
+       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());
 }
 
 
@@ -239,24 +322,31 @@ void PolicyEngine::evaluateDocOpen(CFURLRef path, SecAssessmentFlags flags, CFDi
                                || CFEqual(riskCategory, kLSRiskCategoryUnknown)
                                || CFEqual(riskCategory, kLSRiskCategoryMayContainUnsafeExecutable)) {
                                cfadd(result, "{%O=#T}", kSecAssessmentAssessmentVerdict);
+                               addAuthority(result, "_XProtect");
+                       } else if (FileQuarantine(cfString(path).c_str()).flag(QTN_FLAG_ASSESSMENT_OK)) {
+                               cfadd(result, "{%O=#T}", kSecAssessmentAssessmentVerdict);
+                               addAuthority(result, "Prior Assessment");
                        } else {
                                cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict);
+                               addAuthority(result, "_XProtect");
                        }
-                       addAuthority(result, "_XProtect");
                        addToAuthority(result, kLSDownloadRiskCategoryKey, riskCategory);
                        return;
                }
        }
        // insufficient information from LS - deny by default
-       cfadd(result, "{%O=%F}", kSecAssessmentAssessmentVerdict);
-       addAuthority(result, NULL);
+       cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict);
+       addAuthority(result, "Insufficient Context");
 }
 
 
+//
+// Result-creation helpers
+//
 void PolicyEngine::addAuthority(CFMutableDictionaryRef parent, const char *label, SQLite::int64 row, CFTypeRef cacheInfo)
 {
        CFRef<CFMutableDictionaryRef> auth = makeCFMutableDictionary();
-       if (label)
+       if (label && label[0])
                cfadd(auth, "{%O=%s}", kSecAssessmentAssessmentSource, label);
        if (row)
                CFDictionaryAddValue(auth, kSecAssessmentAssessmentAuthorityRow, CFTempNumber(row));
@@ -276,47 +366,180 @@ void PolicyEngine::addToAuthority(CFMutableDictionaryRef parent, CFStringRef key
 //
 // Add a rule to the policy database
 //
-bool PolicyEngine::add(CFURLRef path, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context)
+bool PolicyEngine::add(CFTypeRef inTarget, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context)
 {
-       if (type != kAuthorityExecute)
-               MacOSError::throwMe(errSecCSUnimplemented);
+       // default type to execution
+       if (type == kAuthorityInvalid)
+               type = kAuthorityExecute;
 
-       double priority = 0;
-       string label;
-       if (context) {
-               if (CFTypeRef pri = CFDictionaryGetValue(context, kSecAssessmentUpdateKeyPriority)) {
-                       if (CFGetTypeID(pri) != CFNumberGetTypeID())
-                               MacOSError::throwMe(errSecCSBadDictionaryFormat);
-                       CFNumberGetValue(CFNumberRef(pri), kCFNumberDoubleType, &priority);
-               }
-               if (CFTypeRef lab = CFDictionaryGetValue(context, kSecAssessmentUpdateKeyLabel)) {
-                       if (CFGetTypeID(lab) != CFStringGetTypeID())
-                               MacOSError::throwMe(errSecCSBadDictionaryFormat);
-                       label = cfString(CFStringRef(lab));
-               }
+       authorizeUpdate(flags, context);
+       CFDictionary ctx(context, errSecCSInvalidAttributeValues);
+       CFCopyRef<CFTypeRef> target = inTarget;
+       
+       if (type == kAuthorityOpenDoc) {
+               // handle document-open differently: use quarantine flags for whitelisting
+               if (!target || CFGetTypeID(target) != CFURLGetTypeID())
+                       MacOSError::throwMe(errSecCSInvalidObjectRef);
+               std::string spath = cfString(target.as<CFURLRef>()).c_str();
+               FileQuarantine qtn(spath.c_str());
+               qtn.setFlag(QTN_FLAG_ASSESSMENT_OK);
+               qtn.applyTo(spath.c_str());
+               return true;
        }
+       
+       if (type == kAuthorityInstall) {
+               return cfmake<CFDictionaryRef>("{%O=%O}", kSecAssessmentAssessmentAuthorityOverride, CFSTR("virtual install"));
+       }
+       
+       // resolve URLs to Requirements
+       normalizeTarget(target, ctx, true);
+       
+       // if we now have anything else, we're busted
+       if (!target || CFGetTypeID(target) != SecRequirementGetTypeID())
+               MacOSError::throwMe(errSecCSInvalidObjectRef);
 
-       CFRef<SecStaticCodeRef> code;
-       MacOSError::check(SecStaticCodeCreateWithPath(path, kSecCSDefaultFlags, &code.aref()));
-       CFRef<CFDictionaryRef> info;
-       MacOSError::check(SecCodeCopySigningInformation(code, kSecCSDefaultFlags, &info.aref()));
-       CFRef<SecRequirementRef> dr;
-       MacOSError::check(SecCodeCopyDesignatedRequirement(code, kSecCSDefaultFlags, &dr.aref()));
+       double priority = 0;
+       string label;
+       bool allow = true;
+       double expires = never;
+       string remarks;
+       
+       if (CFNumberRef pri = ctx.get<CFNumberRef>(kSecAssessmentUpdateKeyPriority))
+               CFNumberGetValue(pri, kCFNumberDoubleType, &priority);
+       if (CFStringRef lab = ctx.get<CFStringRef>(kSecAssessmentUpdateKeyLabel))
+               label = cfString(lab);
+       if (CFDateRef time = ctx.get<CFDateRef>(kSecAssessmentUpdateKeyExpires))
+               // we're using Julian dates here; convert from CFDate
+               expires = CFDateGetAbsoluteTime(time) / 86400.0 + 2451910.5;
+       if (CFBooleanRef allowing = ctx.get<CFBooleanRef>(kSecAssessmentUpdateKeyAllow))
+               allow = allowing == kCFBooleanTrue;
+       if (CFStringRef rem = ctx.get<CFStringRef>(kSecAssessmentUpdateKeyRemarks))
+               remarks = cfString(rem);
 
        CFRef<CFStringRef> requirementText;
-       MacOSError::check(SecRequirementCopyString(dr, kSecCSDefaultFlags, &requirementText.aref()));
+       MacOSError::check(SecRequirementCopyString(target.as<SecRequirementRef>(), kSecCSDefaultFlags, &requirementText.aref()));
+       SQLite::Transaction xact(*this, SQLite3::Transaction::deferred, "add_rule");
        SQLite::Statement insert(*this,
-               "INSERT INTO authority (type, allow, requirement, priority, label) VALUES (?1, ?2, ?3, ?4, ?5);");
-       insert.bind(1).integer(type);
-       insert.bind(2).integer(true);
-       insert.bind(3) = requirementText.get();
-       insert.bind(4) = priority;
-       insert.bind(5) = label.c_str();
+               "INSERT INTO authority (type, allow, requirement, priority, label, expires, remarks)"
+               "       VALUES (:type, :allow, :requirement, :priority, :label, :expires, :remarks);");
+       insert.bind(":type").integer(type);
+       insert.bind(":allow").integer(allow);
+       insert.bind(":requirement") = requirementText.get();
+       insert.bind(":priority") = priority;
+       if (!label.empty())
+               insert.bind(":label") = label.c_str();
+       insert.bind(":expires") = expires;
+       if (!remarks.empty())
+               insert.bind(":remarks") = remarks.c_str();
        insert.execute();
+       this->purgeObjects(priority);
+       xact.commit();
+       notify_post(kNotifySecAssessmentUpdate);
        return true;
 }
 
 
+//
+// Perform an action on existing authority rule(s)
+//
+bool PolicyEngine::manipulateRules(const std::string &stanza,
+       CFTypeRef inTarget, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context)
+{
+       authorizeUpdate(flags, context);
+       CFDictionary ctx(context, errSecCSInvalidAttributeValues);
+       CFCopyRef<CFTypeRef> target = inTarget;
+       normalizeTarget(target, ctx);
+
+       string label;
+       
+       if (CFStringRef lab = ctx.get<CFStringRef>(kSecAssessmentUpdateKeyLabel))
+               label = cfString(CFStringRef(lab));
+
+       SQLite::Transaction xact(*this, SQLite3::Transaction::deferred, "rule_change");
+       SQLite::Statement action(*this);
+       if (!target) {
+               if (label.empty())      // underspecified
+                       MacOSError::throwMe(errSecCSInvalidObjectRef);
+               if (type == kAuthorityInvalid) {
+                       action.query(stanza + " WHERE label = :label");
+               } else {
+                       action.query(stanza + " WHERE type = :type AND label = :label");
+                       action.bind(":type").integer(type);
+               }
+               action.bind(":label") = label.c_str();
+       } else if (CFGetTypeID(target) == CFNumberGetTypeID()) {
+               action.query(stanza + " WHERE id = :id");
+               action.bind(":id").integer(cfNumber<uint64_t>(target.as<CFNumberRef>()));
+       } else if (CFGetTypeID(target) == SecRequirementGetTypeID()) {
+               if (type == kAuthorityInvalid)
+                       type = kAuthorityExecute;
+               CFRef<CFStringRef> requirementText;
+               MacOSError::check(SecRequirementCopyString(target.as<SecRequirementRef>(), kSecCSDefaultFlags, &requirementText.aref()));
+               action.query(stanza + " WHERE type = :type AND requirement = :requirement");
+               action.bind(":type").integer(type);
+               action.bind(":requirement") = requirementText.get();
+       } else
+               MacOSError::throwMe(errSecCSInvalidObjectRef);
+
+       action.execute();
+       unsigned int changes = this->changes(); // latch change count
+       // We MUST purge objects with priority <= MAX(priority of any changed rules);
+       // but for now we just get lazy and purge them ALL.
+       if (changes) {
+               this->purgeObjects(1.0E100);
+               xact.commit();
+               notify_post(kNotifySecAssessmentUpdate);
+               return true;
+       }
+       // no change; return an error
+       MacOSError::throwMe(errSecCSNoMatches);
+}
+
+
+bool PolicyEngine::remove(CFTypeRef target, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context)
+{
+       if (type == kAuthorityOpenDoc) {
+               // handle document-open differently: use quarantine flags for whitelisting
+               authorizeUpdate(flags, context);
+               if (!target || CFGetTypeID(target) != CFURLGetTypeID())
+                       MacOSError::throwMe(errSecCSInvalidObjectRef);
+               std::string spath = cfString(CFURLRef(target)).c_str();
+               FileQuarantine qtn(spath.c_str());
+               qtn.clearFlag(QTN_FLAG_ASSESSMENT_OK);
+               qtn.applyTo(spath.c_str());
+               return true;
+       }
+       return manipulateRules("DELETE FROM authority", target, type, flags, context);
+}
+
+bool PolicyEngine::enable(CFTypeRef target, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context)
+{
+       return manipulateRules("UPDATE authority SET disabled = 0", target, type, flags, context);
+}
+
+bool PolicyEngine::disable(CFTypeRef target, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context)
+{
+       return manipulateRules("UPDATE authority SET disabled = 1", target, type, flags, context);
+}
+
+
+bool PolicyEngine::update(CFTypeRef target, SecAssessmentFlags flags, CFDictionaryRef context)
+{
+       AuthorityType type = typeFor(context, kAuthorityInvalid);
+       CFStringRef edit = CFStringRef(CFDictionaryGetValue(context, kSecAssessmentContextKeyUpdate));
+       if (CFEqual(edit, kSecAssessmentUpdateOperationAdd))
+               return this->add(target, type, flags, context);
+       else if (CFEqual(edit, kSecAssessmentUpdateOperationRemove))
+               return this->remove(target, type, flags, context);
+       else if (CFEqual(edit, kSecAssessmentUpdateOperationEnable))
+               return this->enable(target, type, flags, context);
+       else if (CFEqual(edit, kSecAssessmentUpdateOperationDisable))
+               return this->disable(target, type, flags, context);
+       else
+               MacOSError::throwMe(errSecCSInvalidAttributeValues);
+}
+
+
 //
 // Fill in extra information about the originator of cryptographic credentials found - if any
 //
@@ -333,30 +556,98 @@ void PolicyEngine::setOrigin(CFArrayRef chain, CFMutableDictionaryRef result)
 //
 // Take an assessment outcome and record it in the object cache
 //
-void PolicyEngine::recordOutcome(SecStaticCodeRef code, bool allow, AuthorityType type, time_t expires, int authority, const char *label)
+void PolicyEngine::recordOutcome(SecStaticCodeRef code, bool allow, AuthorityType type, double expires, int authority)
 {
        CFRef<CFDictionaryRef> info;
        MacOSError::check(SecCodeCopySigningInformation(code, kSecCSDefaultFlags, &info.aref()));
        CFDataRef cdHash = CFDataRef(CFDictionaryGetValue(info, kSecCodeInfoUnique));
+       assert(cdHash);         // was signed
        CFRef<CFURLRef> path;
        MacOSError::check(SecCodeCopyPath(code, kSecCSDefaultFlags, &path.aref()));
-       //@@@ should really be OR REPLACE IF EXPIRED... does it matter? @@@
+       assert(expires);
        SQLite::Transaction xact(*this, SQLite3::Transaction::deferred, "caching");
        SQLite::Statement insert(*this,
-               "INSERT OR REPLACE INTO object (type, allow, hash, expires, authority, label, path) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)");
-       insert.bind(1).integer(type);
-       insert.bind(2).integer(allow);
-       insert.bind(3) = cdHash;
-       if (expires)
-               insert.bind(4).integer(expires);
-       insert.bind(5).integer(authority);
-       if (label)
-               insert.bind(6) = label;
-       insert.bind(7) = cfString(path).c_str();
+               "INSERT OR REPLACE INTO object (type, allow, hash, expires, path, authority)"
+               "       VALUES (:type, :allow, :hash, :expires, :path,"
+               "       CASE :authority WHEN 0 THEN (SELECT id FROM authority WHERE label = 'No Matching Rule') ELSE :authority END"
+               "       );");
+       insert.bind(":type").integer(type);
+       insert.bind(":allow").integer(allow);
+       insert.bind(":hash") = cdHash;
+       insert.bind(":expires") = expires;
+       insert.bind(":path") = cfString(path).c_str();
+       insert.bind(":authority").integer(authority);
        insert.execute();
        xact.commit();
 }
 
 
+//
+// Perform update authorization processing.
+// Throws an exception if authorization is denied.
+//
+static void authorizeUpdate(SecCSFlags flags, CFDictionaryRef context)
+{
+       AuthorizationRef authorization = NULL;
+       
+       if (context)
+               if (CFTypeRef authkey = CFDictionaryGetValue(context, kSecAssessmentUpdateKeyAuthorization))
+                       if (CFGetTypeID(authkey) == CFDataGetTypeID()) {
+                               CFDataRef authdata = CFDataRef(authkey);
+                               MacOSError::check(AuthorizationCreateFromExternalForm((AuthorizationExternalForm *)CFDataGetBytePtr(authdata), &authorization));
+                       }
+       if (authorization == NULL)
+               MacOSError::check(AuthorizationCreate(NULL, NULL, kAuthorizationFlagDefaults, &authorization));
+       
+       AuthorizationItem right[] = {
+               { "com.apple.security.assessment.update", 0, NULL, 0 }
+       };
+       AuthorizationRights rights = { sizeof(right) / sizeof(right[0]), right };
+       MacOSError::check(AuthorizationCopyRights(authorization, &rights, NULL,
+               kAuthorizationFlagExtendRights | kAuthorizationFlagInteractionAllowed, NULL));
+       
+       MacOSError::check(AuthorizationFree(authorization, kAuthorizationFlagDefaults));
+}
+
+
+//
+// Perform common argument normalizations for update operations
+//
+static void normalizeTarget(CFRef<CFTypeRef> &target, CFDictionary &context, bool signUnsigned)
+{
+       // turn CFURLs into (designated) SecRequirements
+       if (target && CFGetTypeID(target) == CFURLGetTypeID()) {
+               CFRef<SecStaticCodeRef> code;
+               MacOSError::check(SecStaticCodeCreateWithPath(target.as<CFURLRef>(), kSecCSDefaultFlags, &code.aref()));
+               CFRef<SecRequirementRef> requirement;
+               switch (OSStatus rc = SecCodeCopyDesignatedRequirement(code, kSecCSDefaultFlags, (SecRequirementRef *)&target.aref())) {
+               case noErr:
+                       break;
+               case errSecCSUnsigned:
+                       if (signUnsigned) {
+                               // Ad-hoc sign the code in the system database. This requires root privileges.
+                               CFRef<SecCodeSignerRef> signer;
+                               CFTemp<CFDictionaryRef> arguments("{%O=#N, %O=#N}", kSecCodeSignerDetached, kSecCodeSignerIdentity);
+                               MacOSError::check(SecCodeSignerCreate(arguments, kSecCSDefaultFlags, &signer.aref()));
+                               MacOSError::check(SecCodeSignerAddSignature(signer, code, kSecCSDefaultFlags));
+                               MacOSError::check(SecCodeCopyDesignatedRequirement(code, kSecCSDefaultFlags, (SecRequirementRef *)&target.aref()));
+                               break;
+                       }
+                       // fall through
+               default:
+                       MacOSError::check(rc);
+               }
+               if (context.get(kSecAssessmentUpdateKeyRemarks) == NULL)        {
+                       // no explicit remarks; add one with the path
+                       CFRef<CFURLRef> path;
+                       MacOSError::check(SecCodeCopyPath(code, kSecCSDefaultFlags, &path.aref()));
+                       CFMutableDictionaryRef dict = makeCFMutableDictionary(context.get());
+                       CFDictionaryAddValue(dict, kSecAssessmentUpdateKeyRemarks, CFTempString(cfString(path)));
+                       context.take(dict);
+               }
+       }
+}
+
+
 } // end namespace CodeSigning
 } // end namespace Security
index 73b993a4e659b5b71bc61800b3c792a87a93c7df..e22f50d4edc029a37c72777ce355398b28de241e 100644 (file)
@@ -52,8 +52,14 @@ public:
 
 public:
        void evaluate(CFURLRef path, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result);
-       bool add(CFURLRef path, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context);
 
+       bool update(CFTypeRef target, SecAssessmentFlags flags, CFDictionaryRef context);
+       bool add(CFTypeRef target, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context);
+       bool remove(CFTypeRef target, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context);
+       bool enable(CFTypeRef target, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context);
+       bool disable(CFTypeRef target, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context);
+
+public:
        static void addAuthority(CFMutableDictionaryRef parent, const char *label, SQLite::int64 row = 0, CFTypeRef cacheInfo = NULL);
        static void addToAuthority(CFMutableDictionaryRef parent, CFStringRef key, CFTypeRef value);
 
@@ -61,10 +67,13 @@ private:
        void evaluateCode(CFURLRef path, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result);
        void evaluateInstall(CFURLRef path, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result);
        void evaluateDocOpen(CFURLRef path, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result);
+       
+       bool manipulateRules(const std::string &stanza,
+               CFTypeRef target, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context);
 
        void setOrigin(CFArrayRef chain, CFMutableDictionaryRef result);
 
-       void recordOutcome(SecStaticCodeRef code, bool allow, AuthorityType type, time_t expires, int authority, const char *label);
+       void recordOutcome(SecStaticCodeRef code, bool allow, AuthorityType type, double expires, int authority);
 };
 
 
diff --git a/lib/quarantine++.cpp b/lib/quarantine++.cpp
new file mode 100644 (file)
index 0000000..866627d
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2011 Apple Inc. All Rights Reserved.
+ * 
+ * @APPLE_LICENSE_HEADER_START@
+ * 
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ * 
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ * 
+ * @APPLE_LICENSE_HEADER_END@
+ */
+
+//
+// xar++ - interface to XAR-format archive files
+//
+#include "quarantine++.h"
+
+
+namespace Security {
+namespace CodeSigning {
+
+
+//
+// Check the int result of a qtn API call.
+// If the error is "not quarantined," note in the object (no error).
+// Other qtn-specific errors are arbitrarily mapped to ENOSYS (this isn't
+// important enough to subclass CommonError).
+//
+void FileQuarantine::check(int err)
+{
+       switch (err) {
+       case 0:
+               mQuarantined = true;
+               break;
+       case QTN_NOT_QUARANTINED:
+               mQuarantined = false;
+               return;
+       default:        // some flavor of quarantine-not-available
+               UnixError::throwMe(err);
+       }
+}
+
+
+FileQuarantine::~FileQuarantine()
+{
+       if (mQtn)
+               qtn_file_free(mQtn);
+}
+
+
+FileQuarantine::FileQuarantine(const char *path)
+{
+       if (!(mQtn = qtn_file_alloc()))
+               UnixError::throwMe();
+       check(qtn_file_init_with_path(mQtn, path));
+}
+
+FileQuarantine::FileQuarantine(int fd)
+{
+       if (!(mQtn = qtn_file_alloc()))
+               UnixError::throwMe();
+       check(qtn_file_init_with_fd(mQtn, fd));
+}
+
+
+void FileQuarantine::setFlags(uint32_t flags)
+{
+       if (mQuarantined)
+               check(qtn_file_set_flags(mQtn, flags));
+}
+
+void FileQuarantine::setFlag(uint32_t flag)
+{
+       if (mQuarantined)
+               setFlags(flags() | flag);
+}
+
+void FileQuarantine::clearFlag(uint32_t flag)
+{
+       if (mQuarantined)
+               setFlags(flags() & ~flag);
+}
+
+void FileQuarantine::applyTo(const char *path)
+{
+       check(qtn_file_apply_to_path(mQtn, path));
+}
+
+void FileQuarantine::applyTo(int fd)
+{
+       check(qtn_file_apply_to_fd(mQtn, fd));
+}
+
+
+} // end namespace CodeSigning
+} // end namespace Security
diff --git a/lib/quarantine++.h b/lib/quarantine++.h
new file mode 100644 (file)
index 0000000..53c5609
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2011 Apple Inc. All Rights Reserved.
+ * 
+ * @APPLE_LICENSE_HEADER_START@
+ * 
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ * 
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ * 
+ * @APPLE_LICENSE_HEADER_END@
+ */
+
+//
+// quarantine++ - interface to XAR-format archive files
+//
+#ifndef _H_QUARANTINEPLUSPLUS
+#define _H_QUARANTINEPLUSPLUS
+
+#include <security_utilities/utilities.h>
+#include <CoreFoundation/CoreFoundation.h>
+
+extern "C" {
+#include <quarantine.h>
+}
+
+namespace Security {
+namespace CodeSigning {
+
+
+//
+// A file quarantine object
+//
+class FileQuarantine {
+public:        
+       FileQuarantine(const char *path);
+       FileQuarantine(int fd);
+       virtual ~FileQuarantine();
+       
+       uint32_t flags() const
+               { return qtn_file_get_flags(mQtn); }
+       bool flag(uint32_t f) const
+               { return this->flags() & f; }
+       
+       void setFlags(uint32_t flags);
+       void setFlag(uint32_t flag);
+       void clearFlag(uint32_t flag);
+       
+       void applyTo(const char *path);
+       void applyTo(int fd);
+       
+       operator bool() const { return mQtn != 0; }
+       bool quarantined() const { return mQuarantined; }
+
+private:
+       void check(int err);
+       
+private:
+       qtn_file_t mQtn;                // qtn handle
+       bool mQuarantined;              // has quarantine information
+};
+
+
+} // end namespace CodeSigning
+} // end namespace Security
+
+#endif // !_H_QUARANTINEPLUSPLUS
index ec6cd64390f37071d5b5e0e316460c51d2700588..ceca15012fc2f3d6c01c06958aeae5b8d99f45a4 100644 (file)
@@ -109,7 +109,7 @@ string Dumper::dump(const Requirement *req, bool debug /* = false */)
        } catch (const CommonError &err) {
                if (debug) {
                        char errstr[80];
-                       snprintf(errstr, sizeof(errstr), " !! error %ld !!", err.osStatus());
+                       snprintf(errstr, sizeof(errstr), " !! error %ld !!", (unsigned long)err.osStatus());
                        return dumper.value() + errstr;
                }
                throw;
index 8558455da7d7001a7ad9ea23515a19c5b4dbc01d..a2ca3952dc6ceff626e183d54262d6cc155e3c65 100644 (file)
@@ -136,10 +136,16 @@ _kSecAssessmentOperationTypeExecute
 _kSecAssessmentOperationTypeInstall
 _kSecAssessmentOperationTypeOpenDocument
 _kSecAssessmentContextKeyUpdate
-_kSecAssessmentUpdateOperationAddFile
-_kSecAssessmentUpdateOperationRemoveFile
+_kSecAssessmentUpdateOperationAdd
+_kSecAssessmentUpdateOperationRemove
+_kSecAssessmentUpdateOperationEnable
+_kSecAssessmentUpdateOperationDisable
+_kSecAssessmentUpdateKeyAuthorization
+_kSecAssessmentUpdateKeyAllow
+_kSecAssessmentUpdateKeyExpires
 _kSecAssessmentUpdateKeyPriority
 _kSecAssessmentUpdateKeyLabel
+_kSecAssessmentUpdateKeyRemarks
 _kSecAssessmentAssessmentAuthority
 _kSecAssessmentAssessmentAuthorityRow
 _kSecAssessmentAssessmentFromCache
index 4e2eb8608dde091cfbc9dd4da160a8e6fbf8a08f..9448908fee75470c44047ee3ec13f6f2aa93c3f4 100644 (file)
 --
 -- This is currently for sqlite3
 --
+-- NOTES:
+-- Dates are uniformly in julian form. We use 5000000 as the canonical "never" expiration
+-- value; that's a day in the year 8977.
+--
+PRAGMA user_version = 1;
 PRAGMA foreign_keys = true;
+PRAGMA legacy_file_format = false;
+PRAGMA recursive_triggers = true;
+
 
+--
+-- The feature table hold configuration features and options
+--
+CREATE TABLE feature (
+       id INTEGER PRIMARY KEY,                 -- canononical
+       name TEXT NOT NULL UNIQUE,                      -- name of option
+       value TEXT NULL,                                        -- value of option, if any
+       remarks TEXT NOT NULL                           -- optional remarks string
+);
 
 
 --
 -- The primary authority. This table is conceptually scanned
--- in priority order, with the highest-priority matching record
+-- in priority order, with the highest-priority matching enabled record
 -- determining the outcome.
 -- 
 CREATE TABLE authority (
-       id INTEGER PRIMARY KEY,
-       type INTEGER NOT NULL,
-       requirement TEXT NOT NULL,
-       allow INTEGER NOT NULL,
-       expires INTEGER NULL,
-       priority REAL NOT NULL DEFAULT (0),
-       label TEXT NULL,
-       inhibit_cache INTEGER NULL,
-       flags INTEGER NOT NULL DEFAULT (0),
+       id INTEGER PRIMARY KEY AUTOINCREMENT,                           -- canonical
+       version INTEGER NOT NULL DEFAULT (1)                            -- semantic version of this rule
+               CHECK (version > 0),
+       type INTEGER NOT NULL,                                                          -- operation type
+       requirement TEXT NULL                                                           -- code requirement
+               CHECK ((requirement IS NULL) = ((flags & 1) != 0)),
+       allow INTEGER NOT NULL DEFAULT (1)                                      -- allow (1) or deny (0)
+               CHECK (allow = 0 OR allow = 1),
+       disabled INTEGER NOT NULL DEFAULT (0)                           -- disable count (stacks; enabled if zero)
+               CHECK (disabled >= 0),
+       expires FLOAT NOT NULL DEFAULT (5000000),                       -- expiration of rule authority (Julian date)
+       priority REAL NOT NULL DEFAULT (0),                             -- rule priority (full float)
+       label TEXT NULL,                                                                        -- text label for authority rule
+       flags INTEGER NOT NULL DEFAULT (0),                             -- amalgamated binary flags
        -- following fields are for documentation only
-       remarks TEXT NULL
+       ctime FLOAT NOT NULL DEFAULT (JULIANDAY('now')),        -- rule creation time (Julian)
+       mtime FLOAT NOT NULL DEFAULT (JULIANDAY('now')),        -- time rule was last changed (Julian)
+       user TEXT NULL,                                                                         -- user requesting this rule (NULL if unknown)
+       remarks TEXT NULL                                                                       -- optional remarks string
 );
 
--- any Apple-signed installers of any kind
-insert into authority (type, allow, priority, label, requirement)
-       values (2, 1, -1, 'Apple Installer', 'anchor apple generic');
+-- index
+CREATE INDEX authority_type ON authority (type);
+CREATE INDEX authority_priority ON authority (priority);
+CREATE INDEX authority_expires ON authority (expires);
+
+-- update mtime if a record is changed
+CREATE TRIGGER authority_update AFTER UPDATE ON authority
+BEGIN
+       UPDATE authority SET mtime = JULIANDAY('now') WHERE id = old.id;
+END;
+
+-- rules that are actively considered
+CREATE VIEW active_authority AS
+SELECT * from authority
+WHERE disabled = 0 AND JULIANDAY('now') < expires AND (flags & 1) = 0;
+
+-- rules subject to priority scan: active_authority but including disabled rules
+CREATE VIEW scan_authority AS
+SELECT * from authority
+WHERE JULIANDAY('now') < expires AND (flags & 1) = 0;
+
+
+--
+-- Initial canonical contents of a fresh database
+--
+
+-- virtual rule anchoring negative cache entries (no rule found)
+insert into authority (type, allow, priority, flags, label)
+       values (1, 0, -1.0E100, 1, 'No Matching Rule');
+
+-- any Apple-signed installers except Developer ID
+insert into authority (type, allow, priority, flags, label, requirement)
+       values (2, 1, -1, 2, 'Apple Installer', 'anchor apple generic and ! certificate 1[field.1.2.840.113635.100.6.2.6]');
 
 -- Apple code signing
-insert into authority (type, allow, label, requirement)
-       values (1, 1, 'Apple', 'anchor apple');
+insert into authority (type, allow, flags, label, requirement)
+       values (1, 1, 2, 'Apple System', 'anchor apple');
 
 -- Mac App Store signing
-insert into authority (type, allow, label, requirement)
-       values (1, 1, 'Mac App Store', 'anchor apple generic and certificate leaf[field.1.2.840.113635.100.6.1.9] exists');
+insert into authority (type, allow, flags, label, requirement)
+       values (1, 1, 2, 'Mac App Store', 'anchor apple generic and certificate leaf[field.1.2.840.113635.100.6.1.9] exists');
 
-insert into authority (type, allow, label, requirement)
-       values (1, 1, 'Developer Seed', 'anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] exists and certificate leaf[field.1.2.840.113635.100.6.1.13] exists');
-insert into authority (type, allow, label, requirement)
-       values (2, 1, 'Developer Seed', 'anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] exists and certificate leaf[field.1.2.840.113635.100.6.1.14] exists');
+-- Caspian code and archive signing
+insert into authority (type, allow, flags, label, requirement)
+       values (1, 1, 2, 'Developer ID', 'anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] exists and certificate leaf[field.1.2.840.113635.100.6.1.13] exists');
+insert into authority (type, allow, flags, label, requirement)
+       values (2, 1, 2, 'Developer ID', 'anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] exists and certificate leaf[field.1.2.840.113635.100.6.1.14] exists');
 
 
 --
@@ -71,17 +127,50 @@ insert into authority (type, allow, label, requirement)
 -- for individual objects (by object hash). Entries come from
 -- full evaluations of authority records, or by explicitly inserting
 -- override rules that preempt the normal authority.
+-- EACH object record must have a parent authority record from which it is derived;
+-- this may be a normal authority rule or an override rule. If the parent rule is deleted,
+-- all objects created from it are automatically removed (by sqlite itself).
 --
 CREATE TABLE object (
-       id INTEGER PRIMARY KEY,
-       type INTEGER NOT NULL,
-       hash CDHASH NOT NULL UNIQUE,
-       allow INTEGER NOT NULL,
-       expires INTEGER NULL,
-       label TEXT NULL,
-       authority INTEGER NULL REFERENCES authority(id),
+       id INTEGER PRIMARY KEY,                                                         -- canonical
+       type INTEGER NOT NULL,                                                                  -- operation type
+       hash CDHASH NOT NULL,                                                                   -- canonical hash of object
+       allow INTEGER NOT NULL,                                                         -- allow (1) or deny (0)
+       expires FLOAT NOT NULL DEFAULT (5000000),                               -- expiration of object entry
+       authority INTEGER NOT NULL                                                              -- governing authority rule
+               REFERENCES authority(id) ON DELETE CASCADE,
        -- following fields are for documentation only
-       path TEXT NULL,
-       created INTEGER NOT NULL default (strftime('%s','now')),
-       remarks TEXT NULL
+       path TEXT NULL,                                                                                 -- path of object at record creation time
+       ctime FLOAT NOT NULL DEFAULT (JULIANDAY('now')),                -- record creation time
+       mtime FLOAT NOT NULL DEFAULT (JULIANDAY('now')),                -- record modification time
+       remarks TEXT NULL                                                                               -- optional remarks string
 );
+
+-- index
+CREATE INDEX object_type ON object (type);
+CREATE INDEX object_expires ON object (expires);
+CREATE UNIQUE INDEX object_hash ON object (hash);
+
+-- update mtime if a record is changed
+CREATE TRIGGER object_update AFTER UPDATE ON object
+BEGIN
+       UPDATE object SET mtime = JULIANDAY('now') WHERE id = old.id;
+END;
+
+
+--
+-- Some useful views on objects. These are for administration; they are not used by the assessor.
+--
+CREATE VIEW object_state AS
+SELECT object.id, object.type, object.allow,
+       CASE object.expires WHEN 5000000 THEN NULL ELSE STRFTIME('%Y-%m-%d %H:%M:%f', object.expires, 'localtime') END AS expiration,
+       (object.expires - JULIANDAY('now')) * 86400 as remaining,
+       authority.label,
+       object.authority,
+       object.path,
+       object.ctime,
+       authority.requirement,
+       authority.disabled,
+       object.remarks
+FROM object, authority
+WHERE object.authority = authority.id;
index b9ace0fac176b9e2c932c18ac83fc48c0b6a3fde..64fbcf9d8b2fe898eb185339d519e5fcae5b59f5 100644 (file)
@@ -25,6 +25,7 @@
 #include <syslog.h>
 #include <CoreFoundation/CoreFoundation.h>
 #include <security_utilities/cfutilities.h>
+#include <Security/CodeSigning.h>
 
 
 namespace Security {
@@ -100,7 +101,7 @@ public:
 static void copyCFDictionary(const void *key, const void *value, void *ctx)
 {
        CFMutableDictionaryRef target = CFMutableDictionaryRef(ctx);
-       if (CFEqual(key, kSecAssessmentContextKeyCertificates))         // legacy; drop it
+       if (CFEqual(key, kSecAssessmentContextKeyCertificates)) // obsolete
                return;
        if (CFGetTypeID(value) == CFURLGetTypeID()) {
                CFRef<CFStringRef> path = CFURLCopyFileSystemPath(CFURLRef(value), kCFURLPOSIXPathStyle);
@@ -120,7 +121,12 @@ void xpcEngineAssess(CFURLRef path, uint flags, CFDictionaryRef context, CFMutab
                CFDictionaryApplyFunction(context, copyCFDictionary, ctx);
        CFRef<CFDataRef> contextData = makeCFData(CFDictionaryRef(ctx));
        xpc_dictionary_set_data(msg, "context", CFDataGetBytePtr(contextData), CFDataGetLength(contextData));
+       
        msg.send();
+       
+       if (int64_t error = xpc_dictionary_get_int64(msg, "error"))
+               MacOSError::throwMe(error);
+
        size_t resultLength;
        const void *resultData = xpc_dictionary_get_data(msg, "result", &resultLength);
        CFRef<CFDictionaryRef> resultDict = makeCFDictionaryFrom(resultData, resultLength);
@@ -129,6 +135,48 @@ void xpcEngineAssess(CFURLRef path, uint flags, CFDictionaryRef context, CFMutab
 }
 
 
+bool xpcEngineUpdate(CFTypeRef target, uint flags, CFDictionaryRef context)
+{
+       Message msg("update");
+       // target can be NULL, a CFURLRef, a SecRequirementRef, or a CFNumberRef
+       if (target) {
+               if (CFGetTypeID(target) == CFNumberGetTypeID())
+                       xpc_dictionary_set_uint64(msg, "rule", cfNumber<int64_t>(CFNumberRef(target)));
+               else if (CFGetTypeID(target) == CFURLGetTypeID())
+                       xpc_dictionary_set_string(msg, "url", cfString(CFURLRef(target)).c_str());
+               else if (CFGetTypeID(target) == SecRequirementGetTypeID()) {
+                       CFRef<CFDataRef> data;
+                       MacOSError::check(SecRequirementCopyData(SecRequirementRef(target), kSecCSDefaultFlags, &data.aref()));
+                       xpc_dictionary_set_data(msg, "requirement", CFDataGetBytePtr(data), CFDataGetLength(data));
+               } else
+                       MacOSError::throwMe(errSecCSInvalidObjectRef);
+       }
+       xpc_dictionary_set_int64(msg, "flags", flags);
+       CFRef<CFMutableDictionaryRef> ctx = makeCFMutableDictionary();
+       if (context)
+               CFDictionaryApplyFunction(context, copyCFDictionary, ctx);
+       AuthorizationRef localAuthorization = NULL;
+       if (CFDictionaryGetValue(ctx, kSecAssessmentUpdateKeyAuthorization) == NULL) {  // no caller-provided authorization
+               MacOSError::check(AuthorizationCreate(NULL, NULL, kAuthorizationFlagDefaults, &localAuthorization));
+               AuthorizationExternalForm extForm;
+               MacOSError::check(AuthorizationMakeExternalForm(localAuthorization, &extForm));
+               CFDictionaryAddValue(ctx, kSecAssessmentUpdateKeyAuthorization, CFTempData(&extForm, sizeof(extForm)));
+       }
+       CFRef<CFDataRef> contextData = makeCFData(CFDictionaryRef(ctx));
+       xpc_dictionary_set_data(msg, "context", CFDataGetBytePtr(contextData), CFDataGetLength(contextData));
+       
+       msg.send();
+
+       if (localAuthorization)
+               AuthorizationFree(localAuthorization, kAuthorizationFlagDefaults);
+       
+       if (int64_t error = xpc_dictionary_get_int64(msg, "error"))
+               MacOSError::throwMe(error);
+       
+       return true;
+}
+
+
 bool xpcEngineControl(const char *control)
 {
        Message msg("control");
index 92a6030924cf24345eb52cfb46749e6add19143b..8b2f115d4d9be37764737f3c397d03329f131f77 100644 (file)
@@ -33,6 +33,7 @@ namespace CodeSigning {
 
 
 void xpcEngineAssess(CFURLRef path, uint flags, CFDictionaryRef context, CFMutableDictionaryRef result);
+bool xpcEngineUpdate(CFTypeRef target, uint flags, CFDictionaryRef context);
 bool xpcEngineControl(const char *name);
 
 
index 111f93a3a7c59d5059dae925809a798ac0434c8f..8980156d867e2b66e572ef5eb1eb395b1395cc83 100644 (file)
@@ -13,6 +13,7 @@
                        buildPhases = (
                                C26AC0F0143BCF18001C98CE /* ShellScript */,
                                C26AC0F4143BD1C4001C98CE /* CopyFiles */,
+                               C2F24DFE14BCBBF200309FCD /* ShellScript */,
                        );
                        dependencies = (
                        );
                C2E911E20ADEBE3200275CB2 /* resources.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C2E911E00ADEBE3200275CB2 /* resources.cpp */; };
                C2EF10100A49BD89005A44BB /* renum.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C2EF100E0A49BD89005A44BB /* renum.cpp */; };
                C2EF10130A49BD89005A44BB /* renum.h in Headers */ = {isa = PBXBuildFile; fileRef = C2EF100F0A49BD89005A44BB /* renum.h */; settings = {ATTRIBUTES = (Public, ); }; };
+               C2F4439A14C626D4000A01E6 /* quarantine++.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C2F4439814C626D4000A01E6 /* quarantine++.cpp */; };
+               C2F4439B14C626D4000A01E6 /* quarantine++.h in Headers */ = {isa = PBXBuildFile; fileRef = C2F4439914C626D4000A01E6 /* quarantine++.h */; };
                C2F6566E0BCBFB250078779E /* cserror.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C2F6566C0BCBFB250078779E /* cserror.cpp */; };
                C2F656930BCBFFF40078779E /* cserror.h in Headers */ = {isa = PBXBuildFile; fileRef = C2F6566D0BCBFB250078779E /* cserror.h */; settings = {ATTRIBUTES = (Public, ); }; };
                FEB30C9310DAC89D00557BA2 /* SecTask.c in Sources */ = {isa = PBXBuildFile; fileRef = FEB30C9210DAC89D00557BA2 /* SecTask.c */; };
                C2E911E10ADEBE3200275CB2 /* resources.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = resources.h; sourceTree = "<group>"; };
                C2EF100E0A49BD89005A44BB /* renum.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = renum.cpp; sourceTree = "<group>"; };
                C2EF100F0A49BD89005A44BB /* renum.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = renum.h; sourceTree = "<group>"; };
+               C2F4439814C626D4000A01E6 /* quarantine++.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = "quarantine++.cpp"; sourceTree = "<group>"; };
+               C2F4439914C626D4000A01E6 /* quarantine++.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "quarantine++.h"; sourceTree = "<group>"; };
                C2F6071B107D575700A83618 /* codesign-watch.d */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.dtrace; name = "codesign-watch.d"; path = "dtrace/codesign-watch.d"; sourceTree = SOURCE_ROOT; };
                C2F6566C0BCBFB250078779E /* cserror.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = cserror.cpp; sourceTree = "<group>"; };
                C2F6566D0BCBFB250078779E /* cserror.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = cserror.h; sourceTree = "<group>"; };
                                C2A976A80B8A2E36008B4EA0 /* csutilities.cpp */,
                                C235340E145F1B050073F964 /* xar++.h */,
                                C2353410145F1B110073F964 /* xar++.cpp */,
+                               C2F4439914C626D4000A01E6 /* quarantine++.h */,
+                               C2F4439814C626D4000A01E6 /* quarantine++.cpp */,
                        );
                        name = "Local Utilities";
                        sourceTree = "<group>";
                                C273606F1433F09000A9A5FF /* SecAssessment.h in Headers */,
                                C2A436160F2133B2007A41A6 /* slcrep.h in Headers */,
                                C24EABAB1421432800C16AA9 /* policydb.h in Headers */,
+                               C2F4439B14C626D4000A01E6 /* quarantine++.h in Headers */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
                        );
                        runOnlyForDeploymentPostprocessing = 1;
                        shellPath = /bin/sh;
-                       shellScript = "mkdir -p \"$(dirname \"$SCRIPT_OUTPUT_FILE_0\")\"\nsqlite3 \"$SCRIPT_OUTPUT_FILE_0\" <<END\n.read \"$SCRIPT_INPUT_FILE_0\"\nEND\n";
+                       shellScript = "if [ -f $(TEMPDIR)/SystemPolicy ]; then\n    rm $(TEMPDIR)/SystemPolicy\nfi\nmkdir -p \"$(dirname \"$SCRIPT_OUTPUT_FILE_0\")\"\nsqlite3 \"$SCRIPT_OUTPUT_FILE_0\" <<END\n.read \"$SCRIPT_INPUT_FILE_0\"\nEND\n";
                        showEnvVarsInLog = 0;
                };
                C26AC7080DAEB3A7005BFB40 /* ShellScript */ = {
                        shellPath = /bin/bash;
                        shellScript = "antlr=/usr/local/bin/antlr.jar\nDEBUG=\"\"\nmkdir -p $TEMPDIR\nrm -f $TEMPDIR/Requirement{Parser,Lexer}*\njava -cp \"$antlr\" antlr.Tool -o $TEMPDIR $DEBUG requirements.grammar\nsed -n 's/^.*=\\(\".*\"\\)=.*$/        \\1,/p' $TEMPDIR/RequirementParserTokenTypes.txt >$TEMPDIR/RequirementKeywords.h\n";
                };
+               C2F24DFE14BCBBF200309FCD /* ShellScript */ = {
+                       isa = PBXShellScriptBuildPhase;
+                       buildActionMask = 8;
+                       files = (
+                       );
+                       inputPaths = (
+                               "$(TEMPDIR)/SystemPolicy",
+                       );
+                       outputPaths = (
+                               "$(DSTROOT)/private/var/db/.SystemPolicy-default",
+                       );
+                       runOnlyForDeploymentPostprocessing = 1;
+                       shellPath = /bin/bash;
+                       shellScript = "cp \"$SCRIPT_INPUT_FILE_0\" \"$SCRIPT_OUTPUT_FILE_0\"\nchmod 444 \"$SCRIPT_OUTPUT_FILE_0\"";
+                       showEnvVarsInLog = 0;
+               };
 /* End PBXShellScriptBuildPhase section */
 
 /* Begin PBXSourcesBuildPhase section */
                                C27360D51436866D00A9A5FF /* xpcengine.cpp in Sources */,
                                C2DC2DCA145F594000AD2A3A /* xar++.cpp in Sources */,
                                C2DC2DCB145F5CD000AD2A3A /* policyengine.cpp in Sources */,
+                               C2F4439A14C626D4000A01E6 /* quarantine++.cpp in Sources */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
                        isa = XCBuildConfiguration;
                        buildSettings = {
                                BUILD_VARIANTS = debug;
-                               CURRENT_PROJECT_VERSION = 55032;
+                               CURRENT_PROJECT_VERSION = 55037.4;
                                EXECUTABLE_PREFIX = "";
                                EXECUTABLE_SUFFIX = "";
                                FRAMEWORK_SEARCH_PATHS = (
                                        normal,
                                        debug,
                                );
-                               CURRENT_PROJECT_VERSION = 55032;
+                               CURRENT_PROJECT_VERSION = 55037.4;
                                EXECUTABLE_PREFIX = "";
                                EXECUTABLE_SUFFIX = "";
                                FRAMEWORK_SEARCH_PATHS = (
                                        normal,
                                        debug,
                                );
-                               CURRENT_PROJECT_VERSION = 55032;
+                               CURRENT_PROJECT_VERSION = 55037.4;
                                EXECUTABLE_PREFIX = "";
                                EXECUTABLE_SUFFIX = "";
                                FRAMEWORK_SEARCH_PATHS = (
index f6972e7accbf38e8fe395def575e9de5aeb2d3ed..6b83f053d36a63ec48d22bb81fbdb0139ca1a098 100644 (file)
@@ -266,7 +266,7 @@ appleanchor[Maker &maker]
                        { maker.put(opAppleAnchor); }
        |       "generic"
                        { maker.put(opAppleGenericAnchor); }
-|      |       { string name; } name=identifierString
+       |       { string name; } name=identifierString
                        { maker.put(opNamedAnchor); maker.put(name); }
        ;