]> git.saurik.com Git - apple/security.git/blob - Security/libsecurity_codesigning/lib/opaquewhitelist.cpp
Security-57031.10.10.tar.gz
[apple/security.git] / Security / libsecurity_codesigning / lib / opaquewhitelist.cpp
1 /*
2 * Copyright (c) 2014 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
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
11 * file.
12 *
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.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
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>
33 #include <spawn.h>
34
35 namespace Security {
36 namespace CodeSigning {
37
38 using namespace SQLite;
39
40
41 static std::string hashString(CFDataRef hash);
42 static void attachOpaque(SecStaticCodeRef code, SecAssessmentFeedback feedback);
43
44
45 //
46 // Open the database
47 //
48 OpaqueWhitelist::OpaqueWhitelist(const char *path, int flags)
49 : SQLite::Database(path ? path : opaqueDatabase, flags)
50 {
51 SQLite::Statement createConditions(*this,
52 "CREATE TABLE IF NOT EXISTS conditions ("
53 " label text,"
54 " weight real not null unique,"
55 " source text,"
56 " identifier text,"
57 " version text,"
58 " conditions text not null);"
59 );
60 createConditions.execute();
61 mOverrideQueue = dispatch_queue_create("com.apple.security.assessment.whitelist-override", DISPATCH_QUEUE_SERIAL);
62 }
63
64 OpaqueWhitelist::~OpaqueWhitelist()
65 {
66 dispatch_release(mOverrideQueue);
67 }
68
69
70 //
71 // Check if a code object is whitelisted
72 //
73 bool OpaqueWhitelist::contains(SecStaticCodeRef codeRef, SecAssessmentFeedback feedback, OSStatus reason)
74 {
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());
77
78 CFCopyRef<CFDataRef> current = code->cdHash(); // current cdhash
79 CFDataRef opaque = NULL; // holds computed opaque cdhash
80 bool match = false; // holds final result
81
82 if (!current)
83 return false; // unsigned
84
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);
102 }
103 }
104
105 // compute and attach opaque signature
106 attachOpaque(code->handle(false), feedback);
107 opaque = code->cdHash();
108
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
117 break;
118 }
119 }
120
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) + ")";
126
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')"
133 " LIMIT 1");
134 killswitch.bind(":current") = current.get();
135 if (killswitch.nextRow())
136 enableOverride = false;
137
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",
145 currentHash.c_str(),
146 opaqueHash.c_str(),
147 identifier.c_str(),
148 longVersion.c_str(),
149 NULL // sentinel
150 };
151 int pid, status = 0;
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)
154 override = true;
155 });
156 dispatch_group_wait(group, dispatch_walltime(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC));
157 dispatch_release(group);
158 if (override)
159 match = true;
160 }
161
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);
169 if (!team.empty())
170 trace.add("teamid", "%s", team.c_str());
171 if (cfVersion)
172 trace.add("version", "%s", cfString(cfVersion).c_str());
173 if (cfShortVersion)
174 trace.add("version2", "%s", cfString(cfShortVersion).c_str());
175 if (cfExecutable)
176 trace.add("execname", "%s", cfString(cfExecutable).c_str());
177 trace.send("");
178
179 return match;
180 }
181
182
183 //
184 // Obtain special validation conditions for a static code, based on database configuration.
185 //
186 CFDictionaryRef OpaqueWhitelist::validationConditionsFor(SecStaticCodeRef code)
187 {
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);
201 }
202 if (cfId == NULL) // unsigned; punt
203 return NULL;
204
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"
212 " LIMIT 1"
213 );
214 matches.bind(":source") = team;
215 matches.bind(":identifier") = cfString(cfId);
216 if (cfVersion)
217 matches.bind(":version") = cfString(cfVersion);
218 if (matches.nextRow()) {
219 CFTemp<CFDictionaryRef> conditions((const char*)matches[0]);
220 return conditions.yield();
221 }
222 // no matches
223 return NULL;
224 }
225
226
227 //
228 // Convert a SHA1 hash to a hex string
229 //
230 static std::string hashString(CFDataRef hash)
231 {
232 if (CFDataGetLength(hash) != sizeof(SHA1::Digest)) {
233 return std::string();
234 } else {
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);
240 }
241 }
242
243
244 //
245 // Add a code object to the whitelist
246 //
247 void OpaqueWhitelist::add(SecStaticCodeRef codeRef)
248 {
249 // make our own copy of the code object
250 SecPointer<SecStaticCode> code = new SecStaticCode(SecStaticCode::requiredStatic(codeRef)->diskRep());
251
252 CFCopyRef<CFDataRef> current = code->cdHash();
253 attachOpaque(code->handle(false), NULL); // compute and attach an opaque signature
254 CFDataRef opaque = code->cdHash();
255
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;
259 insert.execute();
260 }
261
262
263 //
264 // Generate and attach an ad-hoc opaque signature
265 //
266 static void attachOpaque(SecStaticCodeRef code, SecAssessmentFeedback feedback)
267 {
268 CFTemp<CFDictionaryRef> rules("{" // same resource rules as used for collection
269 "rules={"
270 "'^.*' = #T"
271 "'^Info\\.plist$' = {omit=#T,weight=10}"
272 "},rules2={"
273 "'^(Frameworks|SharedFrameworks|Plugins|Plug-ins|XPCServices|Helpers|MacOS)/' = {nested=#T, weight=0}"
274 "'^.*' = #T"
275 "'^Info\\.plist$' = {omit=#T,weight=10}"
276 "'^[^/]+$' = {top=#T, weight=0}"
277 "}"
278 "}");
279
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;
288
289 if (feedback)
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);
294 if (!proceed)
295 SecStaticCodeCancelValidation(code, kSecCSDefaultFlags);
296 }
297 return NULL;
298 }));
299
300 MacOSError::check(SecCodeSignerCreate(arguments, creationFlags, &signer.aref()));
301 MacOSError::check(SecCodeSignerAddSignature(signer, code, operationFlags));
302 MacOSError::check(SecCodeSetDetachedSignature(code, signature, kSecCSDefaultFlags));
303 }
304
305
306 } // end namespace CodeSigning
307 } // end namespace Security