2 * Copyright (c) 2014 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
21 * @APPLE_LICENSE_HEADER_END@
23 #include "opaquewhitelist.h"
24 #include "csutilities.h"
25 #include "StaticCode.h"
26 #include <CoreFoundation/CoreFoundation.h>
27 #include <Security/SecCodePriv.h>
28 #include <Security/SecCodeSigner.h>
29 #include <Security/SecStaticCode.h>
30 #include <security_utilities/cfutilities.h>
31 #include <security_utilities/cfmunge.h>
32 #include <CoreFoundation/CFBundlePriv.h>
36 namespace CodeSigning
{
38 using namespace SQLite
;
41 static std::string
hashString(CFDataRef hash
);
42 static void attachOpaque(SecStaticCodeRef code
, SecAssessmentFeedback feedback
);
48 OpaqueWhitelist::OpaqueWhitelist(const char *path
, int flags
)
49 : SQLite::Database(path
? path
: opaqueDatabase
, flags
)
51 SQLite::Statement
createConditions(*this,
52 "CREATE TABLE IF NOT EXISTS conditions ("
54 " weight real not null unique,"
58 " conditions text not null);"
60 createConditions
.execute();
61 mOverrideQueue
= dispatch_queue_create("com.apple.security.assessment.whitelist-override", DISPATCH_QUEUE_SERIAL
);
64 OpaqueWhitelist::~OpaqueWhitelist()
66 dispatch_release(mOverrideQueue
);
71 // Check if a code object is whitelisted
73 bool OpaqueWhitelist::contains(SecStaticCodeRef codeRef
, SecAssessmentFeedback feedback
, OSStatus reason
)
75 // make our own copy of the code object, so we can poke at it without disturbing the original
76 SecPointer
<SecStaticCode
> code
= new SecStaticCode(SecStaticCode::requiredStatic(codeRef
)->diskRep());
78 CFCopyRef
<CFDataRef
> current
= code
->cdHash(); // current cdhash
79 CFDataRef opaque
= NULL
; // holds computed opaque cdhash
80 bool match
= false; // holds final result
83 return false; // unsigned
85 // collect auxiliary information for trace
86 CFRef
<CFDictionaryRef
> info
;
87 std::string team
= "";
88 CFStringRef cfVersion
= NULL
, cfShortVersion
= NULL
, cfExecutable
= NULL
;
89 if (errSecSuccess
== SecCodeCopySigningInformation(code
->handle(false), kSecCSSigningInformation
, &info
.aref())) {
90 if (CFStringRef cfTeam
= CFStringRef(CFDictionaryGetValue(info
, kSecCodeInfoTeamIdentifier
)))
91 team
= cfString(cfTeam
);
92 if (CFDictionaryRef infoPlist
= CFDictionaryRef(CFDictionaryGetValue(info
, kSecCodeInfoPList
))) {
93 if (CFTypeRef version
= CFDictionaryGetValue(infoPlist
, kCFBundleVersionKey
))
94 if (CFGetTypeID(version
) == CFStringGetTypeID())
95 cfVersion
= CFStringRef(version
);
96 if (CFTypeRef shortVersion
= CFDictionaryGetValue(infoPlist
, _kCFBundleShortVersionStringKey
))
97 if (CFGetTypeID(shortVersion
) == CFStringGetTypeID())
98 cfShortVersion
= CFStringRef(shortVersion
);
99 if (CFTypeRef executable
= CFDictionaryGetValue(infoPlist
, kCFBundleExecutableKey
))
100 if (CFGetTypeID(executable
) == CFStringGetTypeID())
101 cfExecutable
= CFStringRef(executable
);
105 // compute and attach opaque signature
106 attachOpaque(code
->handle(false), feedback
);
107 opaque
= code
->cdHash();
109 // lookup current cdhash in whitelist
110 SQLite::Statement
lookup(*this, "SELECT opaque FROM whitelist WHERE current=:current"
111 " AND opaque != 'disable override'");
112 lookup
.bind(":current") = current
.get();
113 while (lookup
.nextRow()) {
114 CFRef
<CFDataRef
> expected
= lookup
[0].data();
115 if (CFEqual(opaque
, expected
)) {
116 match
= true; // actual opaque cdhash matches expected
121 // prepare strings for use inside block
122 std::string currentHash
= hashString(current
);
123 std::string opaqueHash
= hashString(opaque
);
124 std::string identifier
= code
->identifier();
125 std::string longVersion
= cfString(cfShortVersion
) + " (" + cfString(cfVersion
) + ")";
127 // check override killswitch
128 bool enableOverride
= true;
129 SQLite::Statement
killswitch(*this,
130 "SELECT 1 FROM whitelist"
131 " WHERE current='disable override'"
132 " OR (current=:current AND opaque='disable override')"
134 killswitch
.bind(":current") = current
.get();
135 if (killswitch
.nextRow())
136 enableOverride
= false;
138 // allow external program to override decision
139 __block
bool override
= false;
140 if (!match
&& enableOverride
) {
141 dispatch_group_t group
= dispatch_group_create();
142 dispatch_group_async(group
, mOverrideQueue
, ^{
143 const char *argv
[] = {
144 "/usr/libexec/gkoverride",
152 if (posix_spawn(&pid
, argv
[0], NULL
, NULL
, (char **)argv
, NULL
) == 0)
153 if (waitpid(pid
, &status
, 0) == pid
&& WIFEXITED(status
) && WEXITSTATUS(status
) == 42)
156 dispatch_group_wait(group
, dispatch_walltime(DISPATCH_TIME_NOW
, 5 * NSEC_PER_SEC
));
157 dispatch_release(group
);
162 // send a trace indicating the result
163 MessageTrace
trace("com.apple.security.assessment.whitelist2", code
->identifier().c_str());
164 trace
.add("signature2", "%s", currentHash
.c_str());
165 trace
.add("signature3", "%s", opaqueHash
.c_str());
166 trace
.add("result", match
? "pass" : "fail");
167 trace
.add("reason", "%d", reason
);
168 trace
.add("override", "%d", override
);
170 trace
.add("teamid", "%s", team
.c_str());
172 trace
.add("version", "%s", cfString(cfVersion
).c_str());
174 trace
.add("version2", "%s", cfString(cfShortVersion
).c_str());
176 trace
.add("execname", "%s", cfString(cfExecutable
).c_str());
184 // Obtain special validation conditions for a static code, based on database configuration.
186 CFDictionaryRef
OpaqueWhitelist::validationConditionsFor(SecStaticCodeRef code
)
188 // figure out which team key to use
189 std::string team
= "UNKNOWN";
190 CFStringRef cfId
= NULL
;
191 CFStringRef cfVersion
= NULL
;
192 CFRef
<CFDictionaryRef
> info
; // holds lifetimes for the above
193 if (errSecSuccess
== SecCodeCopySigningInformation(code
, kSecCSSigningInformation
, &info
.aref())) {
194 if (CFStringRef cfTeam
= CFStringRef(CFDictionaryGetValue(info
, kSecCodeInfoTeamIdentifier
)))
195 team
= cfString(cfTeam
);
196 cfId
= CFStringRef(CFDictionaryGetValue(info
, kSecCodeInfoIdentifier
));
197 if (CFDictionaryRef infoPlist
= CFDictionaryRef(CFDictionaryGetValue(info
, kSecCodeInfoPList
)))
198 if (CFTypeRef version
= CFDictionaryGetValue(infoPlist
, _kCFBundleShortVersionStringKey
))
199 if (CFGetTypeID(version
) == CFStringGetTypeID())
200 cfVersion
= CFStringRef(version
);
202 if (cfId
== NULL
) // unsigned; punt
205 // find the highest weight matching condition. We perform no merging and the heaviest rule wins
206 SQLite::Statement
matches(*this,
207 "SELECT conditions FROM conditions"
208 " WHERE (source = :source or source IS NULL)"
209 " AND (identifier = :identifier or identifier is NULL)"
210 " AND ((:version IS NULL AND version IS NULL) OR (version = :version OR version IS NULL))"
211 " ORDER BY weight DESC"
214 matches
.bind(":source") = team
;
215 matches
.bind(":identifier") = cfString(cfId
);
217 matches
.bind(":version") = cfString(cfVersion
);
218 if (matches
.nextRow()) {
219 CFTemp
<CFDictionaryRef
> conditions((const char*)matches
[0]);
220 return conditions
.yield();
228 // Convert a SHA1 hash to a hex string
230 static std::string
hashString(CFDataRef hash
)
232 if (CFDataGetLength(hash
) != sizeof(SHA1::Digest
)) {
233 return std::string();
235 const UInt8
*bytes
= CFDataGetBytePtr(hash
);
236 char s
[2 * SHA1::digestLength
+ 1];
237 for (unsigned n
= 0; n
< SHA1::digestLength
; n
++)
238 sprintf(&s
[2*n
], "%2.2x", bytes
[n
]);
239 return std::string(s
);
245 // Add a code object to the whitelist
247 void OpaqueWhitelist::add(SecStaticCodeRef codeRef
)
249 // make our own copy of the code object
250 SecPointer
<SecStaticCode
> code
= new SecStaticCode(SecStaticCode::requiredStatic(codeRef
)->diskRep());
252 CFCopyRef
<CFDataRef
> current
= code
->cdHash();
253 attachOpaque(code
->handle(false), NULL
); // compute and attach an opaque signature
254 CFDataRef opaque
= code
->cdHash();
256 SQLite::Statement
insert(*this, "INSERT OR REPLACE INTO whitelist (current,opaque) VALUES (:current, :opaque)");
257 insert
.bind(":current") = current
.get();
258 insert
.bind(":opaque") = opaque
;
264 // Generate and attach an ad-hoc opaque signature
266 static void attachOpaque(SecStaticCodeRef code
, SecAssessmentFeedback feedback
)
268 CFTemp
<CFDictionaryRef
> rules("{" // same resource rules as used for collection
271 "'^Info\\.plist$' = {omit=#T,weight=10}"
273 "'^(Frameworks|SharedFrameworks|Plugins|Plug-ins|XPCServices|Helpers|MacOS)/' = {nested=#T, weight=0}"
275 "'^Info\\.plist$' = {omit=#T,weight=10}"
276 "'^[^/]+$' = {top=#T, weight=0}"
280 CFRef
<CFDataRef
> signature
= CFDataCreateMutable(NULL
, 0);
281 CFTemp
<CFDictionaryRef
> arguments("{%O=%O, %O=#N, %O=%O}",
282 kSecCodeSignerDetached
, signature
.get(),
283 kSecCodeSignerIdentity
, /* kCFNull, */
284 kSecCodeSignerResourceRules
, rules
.get());
285 CFRef
<SecCodeSignerRef
> signer
;
286 SecCSFlags creationFlags
= kSecCSSignOpaque
| kSecCSSignNoV1
| kSecCSSignBundleRoot
;
287 SecCSFlags operationFlags
= 0;
290 operationFlags
|= kSecCSReportProgress
;
291 MacOSError::check(SecStaticCodeSetCallback(code
, kSecCSDefaultFlags
, NULL
, ^CFTypeRef(SecStaticCodeRef code
, CFStringRef stage
, CFDictionaryRef info
) {
292 if (CFEqual(stage
, CFSTR("progress"))) {
293 bool proceed
= feedback(kSecAssessmentFeedbackProgress
, info
);
295 SecStaticCodeCancelValidation(code
, kSecCSDefaultFlags
);
300 MacOSError::check(SecCodeSignerCreate(arguments
, creationFlags
, &signer
.aref()));
301 MacOSError::check(SecCodeSignerAddSignature(signer
, code
, operationFlags
));
302 MacOSError::check(SecCodeSetDetachedSignature(code
, signature
, kSecCSDefaultFlags
));
306 } // end namespace CodeSigning
307 } // end namespace Security