Security-55471.14.18.tar.gz
[apple/security.git] / 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
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