]>
Commit | Line | Data |
---|---|---|
80e23899 A |
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 | ||
34 | namespace Security { | |
35 | namespace CodeSigning { | |
36 | ||
37 | using namespace SQLite; | |
38 | ||
39 | ||
40 | static void traceHash(MessageTrace &trace, const char *key, CFDataRef hash); | |
41 | static void attachOpaque(SecStaticCodeRef code); | |
42 | ||
43 | ||
44 | // | |
45 | // Open the database | |
46 | // | |
47 | OpaqueWhitelist::OpaqueWhitelist(const char *path, int flags) | |
48 | : SQLite::Database(path ? path : opaqueDatabase, flags) | |
49 | { | |
50 | SQLite::Statement createConditions(*this, | |
51 | "CREATE TABLE IF NOT EXISTS conditions (" | |
52 | " label text," | |
53 | " weight real not null unique," | |
54 | " source text," | |
55 | " identifier text," | |
56 | " version text," | |
57 | " conditions text not null);" | |
58 | ); | |
59 | createConditions.execute(); | |
60 | ||
61 | } | |
62 | ||
63 | OpaqueWhitelist::~OpaqueWhitelist() | |
64 | { /* virtual */ } | |
65 | ||
66 | ||
67 | // | |
68 | // Check if a code object is whitelisted | |
69 | // | |
70 | bool OpaqueWhitelist::contains(SecStaticCodeRef codeRef, OSStatus reason, bool trace) | |
71 | { | |
72 | // make our own copy of the code object, so we can poke at it without disturbing the original | |
73 | SecPointer<SecStaticCode> code = new SecStaticCode(SecStaticCode::requiredStatic(codeRef)->diskRep()); | |
74 | ||
75 | CFCopyRef<CFDataRef> current = code->cdHash(); // current cdhash | |
76 | CFDataRef opaque = NULL; // holds computed opaque cdhash | |
77 | bool match = false; // holds final result | |
78 | ||
79 | if (!current) | |
80 | return false; // unsigned | |
81 | ||
82 | // collect auxiliary information for trace | |
83 | CFRef<CFDictionaryRef> info; | |
84 | std::string team = ""; | |
85 | CFStringRef cfVersion = NULL, cfShortVersion = NULL, cfExecutable = NULL; | |
86 | if (errSecSuccess == SecCodeCopySigningInformation(code->handle(false), kSecCSSigningInformation, &info.aref())) { | |
87 | if (CFStringRef cfTeam = CFStringRef(CFDictionaryGetValue(info, kSecCodeInfoTeamIdentifier))) | |
88 | team = cfString(cfTeam); | |
89 | if (CFDictionaryRef infoPlist = CFDictionaryRef(CFDictionaryGetValue(info, kSecCodeInfoPList))) { | |
90 | if (CFTypeRef version = CFDictionaryGetValue(infoPlist, kCFBundleVersionKey)) | |
91 | if (CFGetTypeID(version) == CFStringGetTypeID()) | |
92 | cfVersion = CFStringRef(version); | |
93 | if (CFTypeRef shortVersion = CFDictionaryGetValue(infoPlist, _kCFBundleShortVersionStringKey)) | |
94 | if (CFGetTypeID(shortVersion) == CFStringGetTypeID()) | |
95 | cfShortVersion = CFStringRef(shortVersion); | |
96 | if (CFTypeRef executable = CFDictionaryGetValue(infoPlist, kCFBundleExecutableKey)) | |
97 | if (CFGetTypeID(executable) == CFStringGetTypeID()) | |
98 | cfExecutable = CFStringRef(executable); | |
99 | } | |
100 | } | |
101 | ||
102 | // compute and attach opaque signature | |
103 | attachOpaque(code->handle(false)); | |
104 | opaque = code->cdHash(); | |
105 | ||
106 | // lookup current cdhash in whitelist | |
107 | SQLite::Statement lookup(*this, "SELECT opaque FROM whitelist WHERE current=:current"); | |
108 | lookup.bind(":current") = current.get(); | |
109 | while (lookup.nextRow()) { | |
110 | CFRef<CFDataRef> expected = lookup[0].data(); | |
111 | if (CFEqual(opaque, expected)) { | |
112 | match = true; // actual opaque cdhash matches expected | |
113 | break; | |
114 | } | |
115 | } | |
116 | ||
117 | if (trace) { | |
118 | // send a trace indicating the result | |
119 | MessageTrace trace("com.apple.security.assessment.whitelist2", code->identifier().c_str()); | |
120 | traceHash(trace, "signature2", current); | |
121 | traceHash(trace, "signature3", opaque); | |
122 | trace.add("result", match ? "pass" : "fail"); | |
123 | trace.add("reason", "%d", reason); | |
124 | if (!team.empty()) | |
125 | trace.add("teamid", "%s", team.c_str()); | |
126 | if (cfVersion) | |
127 | trace.add("version", "%s", cfString(cfVersion).c_str()); | |
128 | if (cfShortVersion) | |
129 | trace.add("version2", "%s", cfString(cfShortVersion).c_str()); | |
130 | if (cfExecutable) | |
131 | trace.add("execname", "%s", cfString(cfExecutable).c_str()); | |
132 | trace.send(""); | |
133 | } | |
134 | ||
135 | return match; | |
136 | } | |
137 | ||
138 | ||
139 | // | |
140 | // Obtain special validation conditions for a static code, based on database configuration. | |
141 | // | |
142 | CFDictionaryRef OpaqueWhitelist::validationConditionsFor(SecStaticCodeRef code) | |
143 | { | |
144 | // figure out which team key to use | |
145 | std::string team = "UNKNOWN"; | |
146 | CFStringRef cfId = NULL; | |
147 | CFStringRef cfVersion = NULL; | |
148 | CFRef<CFDictionaryRef> info; // holds lifetimes for the above | |
149 | if (errSecSuccess == SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref())) { | |
150 | if (CFStringRef cfTeam = CFStringRef(CFDictionaryGetValue(info, kSecCodeInfoTeamIdentifier))) | |
151 | team = cfString(cfTeam); | |
152 | cfId = CFStringRef(CFDictionaryGetValue(info, kSecCodeInfoIdentifier)); | |
153 | if (CFDictionaryRef infoPlist = CFDictionaryRef(CFDictionaryGetValue(info, kSecCodeInfoPList))) | |
154 | if (CFTypeRef version = CFDictionaryGetValue(infoPlist, _kCFBundleShortVersionStringKey)) | |
155 | if (CFGetTypeID(version) == CFStringGetTypeID()) | |
156 | cfVersion = CFStringRef(version); | |
157 | } | |
158 | if (cfId == NULL) // unsigned; punt | |
159 | return NULL; | |
160 | ||
161 | // find the highest weight matching condition. We perform no merging and the heaviest rule wins | |
162 | SQLite::Statement matches(*this, | |
163 | "SELECT conditions FROM conditions" | |
164 | " WHERE (source = :source or source IS NULL)" | |
165 | " AND (identifier = :identifier or identifier is NULL)" | |
166 | " AND ((:version IS NULL AND version IS NULL) OR (version = :version OR version IS NULL))" | |
167 | " ORDER BY weight DESC" | |
168 | " LIMIT 1" | |
169 | ); | |
170 | matches.bind(":source") = team; | |
171 | matches.bind(":identifier") = cfString(cfId); | |
172 | if (cfVersion) | |
173 | matches.bind(":version") = cfString(cfVersion); | |
174 | if (matches.nextRow()) { | |
175 | CFTemp<CFDictionaryRef> conditions((const char*)matches[0]); | |
176 | return conditions.yield(); | |
177 | } | |
178 | // no matches | |
179 | return NULL; | |
180 | } | |
181 | ||
182 | ||
183 | // | |
184 | // Convert a SHA1 hash to hex and add to a trace | |
185 | // | |
186 | static void traceHash(MessageTrace &trace, const char *key, CFDataRef hash) | |
187 | { | |
188 | if (CFDataGetLength(hash) != sizeof(SHA1::Digest)) { | |
189 | trace.add(key, "(unknown format)"); | |
190 | } else { | |
191 | const UInt8 *bytes = CFDataGetBytePtr(hash); | |
192 | char s[2 * SHA1::digestLength + 1]; | |
193 | for (unsigned n = 0; n < SHA1::digestLength; n++) | |
194 | sprintf(&s[2*n], "%2.2x", bytes[n]); | |
195 | trace.add(key, s); | |
196 | } | |
197 | } | |
198 | ||
199 | ||
200 | // | |
201 | // Add a code object to the whitelist | |
202 | // | |
203 | void OpaqueWhitelist::add(SecStaticCodeRef codeRef) | |
204 | { | |
205 | // make our own copy of the code object | |
206 | SecPointer<SecStaticCode> code = new SecStaticCode(SecStaticCode::requiredStatic(codeRef)->diskRep()); | |
207 | ||
208 | CFCopyRef<CFDataRef> current = code->cdHash(); | |
209 | attachOpaque(code->handle(false)); // compute and attach an opaque signature | |
210 | CFDataRef opaque = code->cdHash(); | |
211 | ||
212 | SQLite::Statement insert(*this, "INSERT OR REPLACE INTO whitelist (current,opaque) VALUES (:current, :opaque)"); | |
213 | insert.bind(":current") = current.get(); | |
214 | insert.bind(":opaque") = opaque; | |
215 | insert.execute(); | |
216 | } | |
217 | ||
218 | ||
219 | // | |
220 | // Generate and attach an ad-hoc opaque signature | |
221 | // | |
222 | static void attachOpaque(SecStaticCodeRef code) | |
223 | { | |
224 | CFTemp<CFDictionaryRef> rules("{" // same resource rules as used for collection | |
225 | "rules={" | |
226 | "'^.*' = #T" | |
227 | "'^Info\\.plist$' = {omit=#T,weight=10}" | |
228 | "},rules2={" | |
229 | "'^(Frameworks|SharedFrameworks|Plugins|Plug-ins|XPCServices|Helpers|MacOS)/' = {nested=#T, weight=0}" | |
230 | "'^.*' = #T" | |
231 | "'^Info\\.plist$' = {omit=#T,weight=10}" | |
232 | "'^[^/]+$' = {top=#T, weight=0}" | |
233 | "}" | |
234 | "}"); | |
235 | ||
236 | CFRef<CFDataRef> signature = CFDataCreateMutable(NULL, 0); | |
237 | CFTemp<CFDictionaryRef> arguments("{%O=%O, %O=#N, %O=%O}", | |
238 | kSecCodeSignerDetached, signature.get(), | |
239 | kSecCodeSignerIdentity, /* kCFNull, */ | |
240 | kSecCodeSignerResourceRules, rules.get()); | |
241 | CFRef<SecCodeSignerRef> signer; | |
242 | SecCSFlags flags = kSecCSSignOpaque | kSecCSSignNoV1 | kSecCSSignBundleRoot; | |
243 | MacOSError::check(SecCodeSignerCreate(arguments, flags, &signer.aref())); | |
244 | MacOSError::check(SecCodeSignerAddSignature(signer, code, kSecCSDefaultFlags)); | |
245 | MacOSError::check(SecCodeSetDetachedSignature(code, signature, kSecCSDefaultFlags)); | |
246 | } | |
247 | ||
248 | ||
249 | } // end namespace CodeSigning | |
250 | } // end namespace Security |