2  * Copyright (c) 2011-2012 Apple Inc. All Rights Reserved. 
   4  * @APPLE_LICENSE_HEADER_START@ 
   6  * This file contains Original Code and/or Modifications of Original Code 
   7  * as defined in and that are subject to the Apple Public Source License 
   8  * Version 2.0 (the 'License'). You may not use this file except in 
   9  * compliance with the License. Please obtain a copy of the License at 
  10  * http://www.opensource.apple.com/apsl/ and read it before using this 
  13  * The Original Code and all software distributed under the License are 
  14  * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER 
  15  * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, 
  16  * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, 
  17  * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. 
  18  * Please see the License for the specific language governing rights and 
  19  * limitations under the License. 
  21  * @APPLE_LICENSE_HEADER_END@ 
  25 #include "policyengine.h" 
  26 #include <Security/CodeSigning.h> 
  27 #include <security_utilities/cfutilities.h> 
  28 #include <security_utilities/cfmunge.h> 
  29 #include <security_utilities/blob.h> 
  30 #include <security_utilities/logging.h> 
  31 #include <security_utilities/simpleprefs.h> 
  32 #include <security_utilities/logging.h> 
  33 #include "csdatabase.h" 
  35 #include <dispatch/dispatch.h> 
  36 #include <sys/types.h> 
  41 namespace CodeSigning 
{ 
  44 using namespace SQLite
; 
  48 // Determine the database path 
  50 static const char *dbPath() 
  52         if (const char *s 
= getenv("SYSPOLICYDATABASE")) 
  54         return defaultDatabase
; 
  59 // Help mapping API-ish CFString keys to more convenient internal enumerations 
  62         const CFStringRef 
&cstring
; 
  66 static uint 
mapEnum(CFDictionaryRef context
, CFStringRef attr
, const StringMap 
*map
, uint value 
= 0) 
  69                 if (CFTypeRef value 
= CFDictionaryGetValue(context
, attr
)) 
  70                         for (const StringMap 
*mp 
= map
; mp
->cstring
; ++mp
) 
  71                                 if (CFEqual(mp
->cstring
, value
)) 
  72                                         return mp
->enumeration
; 
  76 static const StringMap mapType
[] = { 
  77         { kSecAssessmentOperationTypeExecute
, kAuthorityExecute 
}, 
  78         { kSecAssessmentOperationTypeInstall
, kAuthorityInstall 
}, 
  79         { kSecAssessmentOperationTypeOpenDocument
, kAuthorityOpenDoc 
}, 
  83 AuthorityType 
typeFor(CFDictionaryRef context
, AuthorityType type 
/* = kAuthorityInvalid */) 
  85         return mapEnum(context
, kSecAssessmentContextKeyOperation
, mapType
, type
); 
  88 CFStringRef 
typeNameFor(AuthorityType type
) 
  90         for (const StringMap 
*mp 
= mapType
; mp
->cstring
; ++mp
) 
  91                 if (type 
== mp
->enumeration
) 
  93         return CFStringCreateWithFormat(NULL
, NULL
, CFSTR("type %d"), type
); 
 100 PolicyDatabase::PolicyDatabase(const char *path
, int flags
) 
 101         : SQLite::Database(path 
? path 
: dbPath(), flags
), 
 102           mLastExplicitCheck(0) 
 104         // sqlite3 doesn't do foreign key support by default, have to turn this on per connection 
 105         SQLite::Statement 
foreign(*this, "PRAGMA foreign_keys = true"); 
 108         // Try upgrade processing if we may be open for write. 
 109         // Ignore any errors (we may have been downgraded to read-only) 
 110         // and try again later. 
 111         if (openFlags() & SQLITE_OPEN_READWRITE
) 
 114                         installExplicitSet(gkeAuthFile
, gkeSigsFile
); 
 119 PolicyDatabase::~PolicyDatabase() 
 124 // Quick-check the cache for a match. 
 125 // Return true on a cache hit, false on failure to confirm a hit for any reason. 
 127 bool PolicyDatabase::checkCache(CFURLRef path
, AuthorityType type
, CFMutableDictionaryRef result
) 
 129         // we currently don't use the cache for anything but execution rules 
 130         if (type 
!= kAuthorityExecute
) 
 133         CFRef
<SecStaticCodeRef
> code
; 
 134         MacOSError::check(SecStaticCodeCreateWithPath(path
, kSecCSDefaultFlags
, &code
.aref())); 
 135         if (SecStaticCodeCheckValidity(code
, kSecCSBasicValidateOnly
, NULL
) != noErr
) 
 136                 return false;   // quick pass - any error is a cache miss 
 137         CFRef
<CFDictionaryRef
> info
; 
 138         MacOSError::check(SecCodeCopySigningInformation(code
, kSecCSDefaultFlags
, &info
.aref())); 
 139         CFDataRef cdHash 
= CFDataRef(CFDictionaryGetValue(info
, kSecCodeInfoUnique
)); 
 141         // check the cache table for a fast match 
 142         SQLite::Statement 
cached(*this, "SELECT object.allow, authority.label, authority FROM object, authority" 
 143                 " WHERE object.authority = authority.id AND object.type = :type AND object.hash = :hash AND authority.disabled = 0" 
 144                 " AND JULIANDAY('now') < object.expires;"); 
 145         cached
.bind(":type").integer(type
); 
 146         cached
.bind(":hash") = cdHash
; 
 147         if (cached
.nextRow()) { 
 148                 bool allow 
= int(cached
[0]); 
 149                 const char *label 
= cached
[1]; 
 150                 SQLite::int64 auth 
= cached
[2]; 
 151                 SYSPOLICY_ASSESS_CACHE_HIT(); 
 153                 // If its allowed, lets do a full validation unless if 
 154                 // we are overriding the assessement, since that force 
 155                 // the verdict to 'pass' at the end 
 157                 if (allow 
&& !overrideAssessment()) 
 158                     MacOSError::check(SecStaticCodeCheckValidity(code
, kSecCSDefaultFlags
, NULL
)); 
 160                 cfadd(result
, "{%O=%B}", kSecAssessmentAssessmentVerdict
, allow
); 
 161                 PolicyEngine::addAuthority(result
, label
, auth
, kCFBooleanTrue
); 
 169 // Purge the object cache of all expired entries. 
 170 // These are meant to run within the caller's transaction. 
 172 void PolicyDatabase::purgeAuthority() 
 174         SQLite::Statement 
cleaner(*this, 
 175                 "DELETE FROM authority WHERE expires <= JULIANDAY('now');"); 
 179 void PolicyDatabase::purgeObjects() 
 181         SQLite::Statement 
cleaner(*this, 
 182                 "DELETE FROM object WHERE expires <= JULIANDAY('now');"); 
 186 void PolicyDatabase::purgeObjects(double priority
) 
 188         SQLite::Statement 
cleaner(*this, 
 189                 "DELETE FROM object WHERE expires <= JULIANDAY('now') OR (SELECT priority FROM authority WHERE id = object.authority) <= :priority;"); 
 190         cleaner
.bind(":priority") = priority
; 
 196 // Database migration 
 198 std::string 
PolicyDatabase::featureLevel(const char *name
) 
 200         SQLite::Statement 
feature(*this, "SELECT value FROM feature WHERE name=:name"); 
 201         feature
.bind(":name") = name
; 
 202         if (feature
.nextRow()) 
 203                 return feature
[0].string(); 
 205                 return "";              // new feature (no level) 
 208 void PolicyDatabase::addFeature(const char *name
, const char *value
, const char *remarks
) 
 210         SQLite::Statement 
feature(*this, "INSERT OR REPLACE INTO feature (name,value,remarks) VALUES(:name, :value, :remarks)"); 
 211         feature
.bind(":name") = name
; 
 212         feature
.bind(":value") = value
; 
 213         feature
.bind(":remarks") = remarks
; 
 217 void PolicyDatabase::simpleFeature(const char *feature
, void (^perform
)()) 
 219         if (!hasFeature(feature
)) { 
 220                 SQLite::Transaction 
update(*this); 
 221                 addFeature(feature
, "upgraded", "upgraded"); 
 227 void PolicyDatabase::simpleFeature(const char *feature
, const char *sql
) 
 229         if (!hasFeature(feature
)) { 
 230                 SQLite::Transaction 
update(*this); 
 231                 addFeature(feature
, "upgraded", "upgraded"); 
 232                 SQLite::Statement 
perform(*this, sql
); 
 239 void PolicyDatabase::upgradeDatabase() 
 241         simpleFeature("bookmarkhints", 
 242                 "CREATE TABLE bookmarkhints (" 
 243                         "  id INTEGER PRIMARY KEY AUTOINCREMENT, " 
 245                         "  authority INTEGER NOT NULL" 
 246                         "     REFERENCES authority(id) ON DELETE CASCADE" 
 249         if (!hasFeature("codesignedpackages")) { 
 250                 SQLite::Transaction 
update(*this); 
 251                 addFeature("codesignedpackages", "upgraded", "upgraded"); 
 252                 SQLite::Statement 
updates(*this, 
 254                                  " SET requirement = 'anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] exists and " 
 255                                  "(certificate leaf[field.1.2.840.113635.100.6.1.14] or certificate leaf[field.1.2.840.113635.100.6.1.13])'" 
 256                                  " WHERE type = 2 and label = 'Developer ID' and flags & :flag"); 
 257                 updates
.bind(":flag") = kAuthorityFlagDefault
; 
 265 // Install Gatekeeper override (GKE) data. 
 266 // The arguments are paths to the authority and signature files. 
 268 void PolicyDatabase::installExplicitSet(const char *authfile
, const char *sigfile
) 
 270         // only try this every gkeCheckInterval seconds 
 271         time_t now 
= time(NULL
); 
 272         if (mLastExplicitCheck 
+ gkeCheckInterval 
> now
) 
 274         mLastExplicitCheck 
= now
; 
 277                 if (CFRef
<CFDataRef
> authData 
= cfLoadFile(authfile
)) { 
 278                         CFDictionary 
auth(CFRef
<CFDictionaryRef
>(makeCFDictionaryFrom(authData
)), errSecCSDbCorrupt
); 
 279                         CFDictionaryRef content 
= auth
.get
<CFDictionaryRef
>(CFSTR("authority")); 
 280                         std::string authUUID 
= cfString(auth
.get
<CFStringRef
>(CFSTR("uuid"))); 
 281                         if (authUUID
.empty()) { 
 282                                 secdebug("gkupgrade", "no uuid in auth file; ignoring gke.auth"); 
 286                         SQLite::Statement 
uuidQuery(*this, "SELECT value FROM feature WHERE name='gke'"); 
 287                         if (uuidQuery
.nextRow()) 
 288                                 dbUUID 
= (const char *)uuidQuery
[0]; 
 289                         if (dbUUID 
== authUUID
) { 
 290                                 secdebug("gkupgrade", "gke.auth already present, ignoring"); 
 293                         Syslog::notice("loading GKE %s (replacing %s)", authUUID
.c_str(), dbUUID
.empty() ? "nothing" : dbUUID
.c_str()); 
 295                         // first, load code signatures. This is pretty much idempotent 
 297                                 if (FILE *sigs 
= fopen(sigfile
, "r")) { 
 299                                         while (const BlobCore 
*blob 
= BlobCore::readBlob(sigs
)) { 
 300                                                 signatureDatabaseWriter().storeCode(blob
, "<remote>"); 
 303                                         secdebug("gkupgrade", "%d detached signature(s) loaded from override data", count
); 
 307                         // start transaction (atomic from here on out) 
 308                         SQLite::Transaction 
loadAuth(*this, SQLite::Transaction::exclusive
, "GKE_Upgrade"); 
 310                         // purge prior authority data 
 311                         SQLite::Statement 
purge(*this, "DELETE FROM authority WHERE flags & :flag"); 
 312                         purge
.bind(":flag") = kAuthorityFlagWhitelist
; 
 316                         CFIndex count 
= CFDictionaryGetCount(content
); 
 317                         CFStringRef keys
[count
]; 
 318                         CFDictionaryRef values
[count
]; 
 319                         CFDictionaryGetKeysAndValues(content
, (const void **)keys
, (const void **)values
); 
 321                         SQLite::Statement 
insert(*this, "INSERT INTO authority (type, allow, requirement, label, flags, remarks)" 
 322                                 " VALUES (:type, 1, :requirement, 'GKE', :flags, :path)"); 
 323                         for (CFIndex n 
= 0; n 
< count
; n
++) { 
 324                                 CFDictionary 
info(values
[n
], errSecCSDbCorrupt
); 
 326                                 insert
.bind(":type") = cfString(info
.get
<CFStringRef
>(CFSTR("type"))); 
 327                                 insert
.bind(":path") = cfString(info
.get
<CFStringRef
>(CFSTR("path"))); 
 328                                 insert
.bind(":requirement") = "cdhash H\"" + cfString(info
.get
<CFStringRef
>(CFSTR("cdhash"))) + "\""; 
 329                                 insert
.bind(":flags") = kAuthorityFlagWhitelist
; 
 333                         // update version and commit 
 334                         addFeature("gke", authUUID
.c_str(), "gke loaded"); 
 338                 secdebug("gkupgrade", "exception during GKE upgrade"); 
 344 // Check the override-enable master flag 
 346 #define SP_ENABLE_KEY CFSTR("enabled") 
 347 #define SP_ENABLED CFSTR("yes") 
 348 #define SP_DISABLED CFSTR("no") 
 350 bool overrideAssessment() 
 352         static bool enabled 
= false; 
 353         static dispatch_once_t once
; 
 354         static int token 
= -1; 
 355         static int have_token 
= 0; 
 356         static dispatch_queue_t queue
; 
 359         if (have_token 
&& notify_check(token
, &check
) == NOTIFY_STATUS_OK 
&& !check
) 
 362         dispatch_once(&once
, ^{ 
 363                 if (notify_register_check(kNotifySecAssessmentMasterSwitch
, &token
) == NOTIFY_STATUS_OK
) 
 365                 queue 
= dispatch_queue_create("com.apple.SecAssessment.assessment", NULL
); 
 368         dispatch_sync(queue
, ^{ 
 369                 /* upgrade configuration from emir, ignore all error since we might not be able to write to */ 
 370                 if (::access(visibleSecurityFlagFile
, F_OK
) == 0) { 
 373                                 ::unlink(visibleSecurityFlagFile
); 
 381                         Dictionary 
* prefsDict 
= Dictionary::CreateDictionary(prefsFile
); 
 382                         if (prefsDict 
== NULL
) 
 385                         CFStringRef value 
= prefsDict
->getStringValue(SP_ENABLE_KEY
); 
 386                         if (value 
&& CFStringCompare(value
, SP_DISABLED
, 0) == 0) 
 398 void setAssessment(bool masterSwitch
) 
 400         MutableDictionary 
*prefsDict 
= MutableDictionary::CreateMutableDictionary(prefsFile
); 
 401         if (prefsDict 
== NULL
) 
 402                 prefsDict 
= new MutableDictionary::MutableDictionary(); 
 403         prefsDict
->setValue(SP_ENABLE_KEY
, masterSwitch 
? SP_ENABLED 
: SP_DISABLED
); 
 404         prefsDict
->writePlistToFile(prefsFile
); 
 407         /* make sure permissions is right */ 
 408         ::chmod(prefsFile
, S_IRUSR 
| S_IWUSR 
| S_IRGRP 
| S_IROTH
); 
 410         notify_post(kNotifySecAssessmentMasterSwitch
); 
 414 } // end namespace CodeSigning 
 415 } // end namespace Security