2 * Copyright (c) 2011-2013 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
, SecAssessmentFlags flags
, 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
) != errSecSuccess
)
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(flags
))
158 MacOSError::check(SecStaticCodeCheckValidity(code
, kSecCSDefaultFlags
, NULL
));
160 cfadd(result
, "{%O=%B}", kSecAssessmentAssessmentVerdict
, allow
);
161 PolicyEngine::addAuthority(flags
, 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 if (const char *value
= feature
[0])
206 return "default"; // old engineering versions may have NULL values; tolerate this
208 return ""; // new feature (no level)
211 void PolicyDatabase::addFeature(const char *name
, const char *value
, const char *remarks
)
213 SQLite::Statement
feature(*this, "INSERT OR REPLACE INTO feature (name,value,remarks) VALUES(:name, :value, :remarks)");
214 feature
.bind(":name") = name
;
215 feature
.bind(":value") = value
;
216 feature
.bind(":remarks") = remarks
;
220 void PolicyDatabase::simpleFeature(const char *feature
, void (^perform
)())
222 SQLite::Transaction
update(*this);
223 if (!hasFeature(feature
)) {
225 addFeature(feature
, "upgraded", "upgraded");
230 void PolicyDatabase::simpleFeature(const char *feature
, const char *sql
)
232 simpleFeature(feature
, ^{
233 SQLite::Statement
perform(*this, sql
);
238 void PolicyDatabase::simpleFeatureNoTransaction(const char *feature
, void (^perform
)())
240 if (!hasFeature(feature
)) {
242 addFeature(feature
, "upgraded", "upgraded");
247 void PolicyDatabase::upgradeDatabase()
249 simpleFeature("bookmarkhints",
250 "CREATE TABLE bookmarkhints ("
251 " id INTEGER PRIMARY KEY AUTOINCREMENT, "
253 " authority INTEGER NOT NULL"
254 " REFERENCES authority(id) ON DELETE CASCADE"
257 simpleFeature("codesignedpackages", ^{
258 SQLite::Statement
update(*this,
260 " SET requirement = 'anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] exists and "
261 "(certificate leaf[field.1.2.840.113635.100.6.1.14] or certificate leaf[field.1.2.840.113635.100.6.1.13])'"
262 " WHERE type = 2 and label = 'Developer ID' and flags & :flag");
263 update
.bind(":flag") = kAuthorityFlagDefault
;
267 simpleFeature("filter_unsigned",
268 "ALTER TABLE authority ADD COLUMN filter_unsigned TEXT NULL"
271 simpleFeature("strict_apple_installer", ^{
272 SQLite::Statement
update(*this,
274 " SET requirement = 'anchor apple generic and certificate 1[subject.CN] = \"Apple Software Update Certification Authority\"'"
275 " WHERE flags & :flag AND label = 'Apple Installer'");
276 update
.bind(":flag") = kAuthorityFlagDefault
;
278 SQLite::Statement
add(*this,
279 "INSERT INTO authority (type, label, flags, requirement)"
280 " VALUES (2, 'Mac App Store', :flags, 'anchor apple generic and certificate leaf[field.1.2.840.113635.100.6.1.10] exists')");
281 add
.bind(":flags") = kAuthorityFlagDefault
;
285 simpleFeature("document rules", ^{
286 SQLite::Statement
addApple(*this,
287 "INSERT INTO authority (type, allow, flags, label, requirement) VALUES (3, 1, 2, 'Apple System', 'anchor apple')");
289 SQLite::Statement
addDevID(*this,
290 "INSERT INTO authority (type, allow, flags, label, requirement) VALUES (3, 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')");
294 simpleFeature("root_only", ^{
295 UnixError::check(::chmod(dbPath(), S_IRUSR
| S_IWUSR
));
298 simpleFeature("notarized_apps", ^{
300 // Insert a set of notarization requirements for notarized applications and installers, with a priority that will be higher than developer id priorities
301 // so they are guaranteed to match first.
302 SQLite::Statement
addNotarizedExecutables(*this,
303 "INSERT INTO authority (type, allow, flags, priority, label, requirement) VALUES (1, 1, 2, 5.0, 'Notarized 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 and notarized')");
304 addNotarizedExecutables
.execute();
306 SQLite::Statement
addNotarizedInstallers(*this,
307 "INSERT INTO authority (type, allow, flags, priority, label, requirement) VALUES (2, 1, 2, 5.0, 'Notarized 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] or certificate leaf[field.1.2.840.113635.100.6.1.13]) and notarized')");
308 addNotarizedInstallers
.execute();
310 // Bump the priority on apple system, apple installer, and mac app store entries so they are evaluated before Developer ID variants.
311 // This is important because notarized variants meet the requirement of the Developer ID variant and would could match that too.
312 SQLite::Statement
bumpAppleSystemPriority(*this,
313 "UPDATE authority SET priority = 20.0 WHERE label = 'Apple System'");
314 bumpAppleSystemPriority
.execute();
316 SQLite::Statement
bumpAppleInstallerPriority(*this,
317 "UPDATE authority SET priority = 20.0 WHERE label = 'Apple Installer'");
318 bumpAppleInstallerPriority
.execute();
320 SQLite::Statement
bumpMacAppStorePriority(*this,
321 "UPDATE authority SET priority = 10.0 WHERE label = 'Mac App Store'");
322 bumpMacAppStorePriority
.execute();
326 SQLite::Transaction
devIdRequirementUpgrades(*this);
328 simpleFeatureNoTransaction("legacy_devid", ^{
329 auto migrateReq
= [](auto db
, int type
, string req
) {
330 const string legacy
=
331 " and (certificate leaf[timestamp.1.2.840.113635.100.6.1.33] absent or "
332 "certificate leaf[timestamp.1.2.840.113635.100.6.1.33] < timestamp \"20190408000000Z\")";
334 const string unnotarized
=
335 " and (certificate leaf[timestamp.1.2.840.113635.100.6.1.33] exists and "
336 "certificate leaf[timestamp.1.2.840.113635.100.6.1.33] >= timestamp \"20190408000000Z\")";
338 SQLite::Statement
update(*db
, "UPDATE OR IGNORE authority "
339 "SET requirement = :newreq "
340 "WHERE requirement = :oldreq "
342 " AND label = 'Developer ID'");
343 update
.bind(":oldreq") = req
;
344 update
.bind(":type") = type
;
345 update
.bind(":newreq") = req
+ legacy
;
348 SQLite::Statement
insert(*db
, "INSERT OR IGNORE INTO authority "
349 "(type, requirement, allow, priority, label) "
351 "(:type, :req, 0, 4.0, "
352 "'Unnotarized Developer ID')");
353 insert
.bind(":type") = type
;
354 insert
.bind(":req") = req
+ unnotarized
;
358 migrateReq(this, 1, "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");
359 migrateReq(this, 2, "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] or certificate leaf[field.1.2.840.113635.100.6.1.13])");
360 migrateReq(this, 3, "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");
363 // Add simpleFeatureNoTransaction for going from the requirements create above, to add secure timestamps in requirements, here before the commit
365 devIdRequirementUpgrades
.commit();
368 simpleFeature("notarized_documents", ^{
369 SQLite::Statement
addNotarizedDocs(*this,
370 "INSERT INTO authority (type, allow, flags, priority, label, requirement) "
371 " VALUES (3, 1, 2, 5.0, 'Notarized Developer ID', "
372 " '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 and notarized')");
373 addNotarizedDocs
.execute();
379 // Install Gatekeeper override (GKE) data.
380 // The arguments are paths to the authority and signature files.
382 void PolicyDatabase::installExplicitSet(const char *authfile
, const char *sigfile
)
384 // only try this every gkeCheckInterval seconds
385 time_t now
= time(NULL
);
386 if (mLastExplicitCheck
+ gkeCheckInterval
> now
)
388 mLastExplicitCheck
= now
;
391 if (CFRef
<CFDataRef
> authData
= cfLoadFile(authfile
)) {
392 CFDictionary
auth(CFRef
<CFDictionaryRef
>(makeCFDictionaryFrom(authData
)), errSecCSDbCorrupt
);
393 CFDictionaryRef content
= auth
.get
<CFDictionaryRef
>(CFSTR("authority"));
394 std::string authUUID
= cfString(auth
.get
<CFStringRef
>(CFSTR("uuid")));
395 if (authUUID
.empty()) {
396 secinfo("gkupgrade", "no uuid in auth file; ignoring gke.auth");
400 SQLite::Statement
uuidQuery(*this, "SELECT value FROM feature WHERE name='gke'");
401 if (uuidQuery
.nextRow())
402 dbUUID
= (const char *)uuidQuery
[0];
403 if (dbUUID
== authUUID
) {
404 secinfo("gkupgrade", "gke.auth already present, ignoring");
407 Syslog::notice("loading GKE %s (replacing %s)", authUUID
.c_str(), dbUUID
.empty() ? "nothing" : dbUUID
.c_str());
409 // first, load code signatures. This is pretty much idempotent
411 if (FILE *sigs
= fopen(sigfile
, "r")) {
413 SignatureDatabaseWriter db
;
414 while (const BlobCore
*blob
= BlobCore::readBlob(sigs
)) {
415 db
.storeCode(blob
, "<remote>");
418 secinfo("gkupgrade", "%d detached signature(s) loaded from override data", count
);
422 // start transaction (atomic from here on out)
423 SQLite::Transaction
loadAuth(*this, SQLite::Transaction::exclusive
, "GKE_Upgrade");
425 // purge prior authority data
426 SQLite::Statement
purge(*this, "DELETE FROM authority WHERE flags & :flag");
427 purge
.bind(":flag") = kAuthorityFlagWhitelist
;
431 CFIndex count
= CFDictionaryGetCount(content
);
432 vector
<CFStringRef
> keys_vector(count
, NULL
);
433 vector
<CFDictionaryRef
> values_vector(count
, NULL
);
434 CFDictionaryGetKeysAndValues(content
, (const void **)keys_vector
.data(), (const void **)values_vector
.data());
436 SQLite::Statement
insert(*this, "INSERT INTO authority (type, allow, requirement, label, filter_unsigned, flags, remarks)"
437 " VALUES (:type, 1, :requirement, 'GKE', :filter, :flags, :path)");
438 for (CFIndex n
= 0; n
< count
; n
++) {
439 CFDictionary
info(values_vector
[n
], errSecCSDbCorrupt
);
440 uint32_t flags
= kAuthorityFlagWhitelist
;
441 if (CFNumberRef versionRef
= info
.get
<CFNumberRef
>("version")) {
442 int version
= cfNumber
<int>(versionRef
);
444 flags
|= kAuthorityFlagWhitelistV2
;
446 flags
|= kAuthorityFlagWhitelistSHA256
;
451 insert
.bind(":type") = cfString(info
.get
<CFStringRef
>(CFSTR("type")));
452 insert
.bind(":path") = cfString(info
.get
<CFStringRef
>(CFSTR("path")));
453 insert
.bind(":requirement") = "cdhash H\"" + cfString(info
.get
<CFStringRef
>(CFSTR("cdhash"))) + "\"";
454 insert
.bind(":filter") = cfString(info
.get
<CFStringRef
>(CFSTR("screen")));
455 insert
.bind(":flags").integer(flags
);
459 // we just changed the authority configuration at priority zero
460 this->purgeObjects(0);
462 // update version and commit
463 addFeature("gke", authUUID
.c_str(), "gke loaded");
465 /* now that we have moved to a bundle for gke files, delete any old style files we find
466 This is really just a best effort cleanup, so we don't care about errors. */
467 if (access(gkeAuthFile_old
, F_OK
) == 0)
469 if (unlink(gkeAuthFile_old
) == 0)
471 Syslog::notice("Deleted old style gke file (%s)", gkeAuthFile_old
);
474 if (access(gkeSigsFile_old
, F_OK
) == 0)
476 if (unlink(gkeSigsFile_old
) == 0)
478 Syslog::notice("Deleted old style gke file (%s)", gkeSigsFile_old
);
483 secinfo("gkupgrade", "exception during GKE upgrade");
489 // Check the override-enable master flag
491 #define SP_ENABLE_KEY CFSTR("enabled")
492 #define SP_ENABLED CFSTR("yes")
493 #define SP_DISABLED CFSTR("no")
495 bool overrideAssessment(SecAssessmentFlags flags
/* = 0 */)
497 static bool enabled
= true;
498 static dispatch_once_t once
;
499 static int token
= -1;
500 static int have_token
= 0;
501 static dispatch_queue_t queue
;
504 if (flags
& kSecAssessmentFlagEnforce
) // explicitly disregard disables (force on)
507 if (have_token
&& notify_check(token
, &check
) == NOTIFY_STATUS_OK
&& !check
)
510 dispatch_once(&once
, ^{
511 if (notify_register_check(kNotifySecAssessmentMasterSwitch
, &token
) == NOTIFY_STATUS_OK
)
513 queue
= dispatch_queue_create("com.apple.SecAssessment.assessment", NULL
);
516 dispatch_sync(queue
, ^{
517 /* upgrade configuration from emir, ignore all error since we might not be able to write to */
518 if (::access(visibleSecurityFlagFile
, F_OK
) == 0) {
521 ::unlink(visibleSecurityFlagFile
);
529 Dictionary
* prefsDict
= Dictionary::CreateDictionary(prefsFile
);
530 if (prefsDict
== NULL
)
533 CFStringRef value
= prefsDict
->getStringValue(SP_ENABLE_KEY
);
534 if (value
&& CFStringCompare(value
, SP_DISABLED
, 0) == 0)
546 void setAssessment(bool masterSwitch
)
548 MutableDictionary
*prefsDict
= MutableDictionary::CreateMutableDictionary(prefsFile
);
549 if (prefsDict
== NULL
)
550 prefsDict
= new MutableDictionary();
551 prefsDict
->setValue(SP_ENABLE_KEY
, masterSwitch
? SP_ENABLED
: SP_DISABLED
);
552 prefsDict
->writePlistToFile(prefsFile
);
555 /* make sure permissions is right */
556 ::chmod(prefsFile
, S_IRUSR
| S_IWUSR
| S_IRGRP
| S_IROTH
);
558 notify_post(kNotifySecAssessmentMasterSwitch
);
560 /* reset the automatic rearm timer */
561 resetRearmTimer("masterswitch");
566 // Reset or query the automatic rearm timer
568 void resetRearmTimer(const char *event
)
570 CFRef
<CFDateRef
> now
= CFDateCreate(NULL
, CFAbsoluteTimeGetCurrent());
571 CFTemp
<CFDictionaryRef
> info("{event=%s, timestamp=%O}", event
, now
.get());
572 CFRef
<CFDataRef
> infoData
= makeCFData(info
.get());
573 UnixPlusPlus::AutoFileDesc
fd(rearmTimerFile
, O_WRONLY
| O_CREAT
| O_TRUNC
, 0644);
574 fd
.write(CFDataGetBytePtr(infoData
), CFDataGetLength(infoData
));
577 bool queryRearmTimer(CFTimeInterval
&delta
)
579 if (CFRef
<CFDataRef
> infoData
= cfLoadFile(rearmTimerFile
)) {
580 if (CFRef
<CFDictionaryRef
> info
= makeCFDictionaryFrom(infoData
)) {
581 CFDateRef timestamp
= (CFDateRef
)CFDictionaryGetValue(info
, CFSTR("timestamp"));
582 if (timestamp
&& CFGetTypeID(timestamp
) == CFDateGetTypeID()) {
583 delta
= CFAbsoluteTimeGetCurrent() - CFDateGetAbsoluteTime(timestamp
);
587 MacOSError::throwMe(errSecCSDbCorrupt
);
593 } // end namespace CodeSigning
594 } // end namespace Security