2 * Copyright (c) 2014-2016 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
111 SQLite::Statement
lookup(*this, "SELECT opaque FROM whitelist WHERE current=:current"
112 " AND opaque != 'disable override'");
113 lookup
.bind(":current") = current
.get();
114 while (lookup
.nextRow()) {
115 CFRef
<CFDataRef
> expected
= lookup
[0].data();
116 if (CFEqual(opaque
, expected
)) {
117 match
= true; // actual opaque cdhash matches expected
123 // prepare strings for use inside block
124 std::string currentHash
= hashString(current
);
125 std::string opaqueHash
= opaque
? hashString(opaque
) : "none";
127 // send a trace indicating the result
128 MessageTrace
trace("com.apple.security.assessment.whitelist2", code
->identifier().c_str());
129 trace
.add("signature2", "%s", currentHash
.c_str());
130 trace
.add("signature3", "%s", opaqueHash
.c_str());
131 trace
.add("result", match
? "pass" : "fail");
132 trace
.add("reason", "%d", (int)reason
);
134 trace
.add("teamid", "%s", team
.c_str());
136 trace
.add("version", "%s", cfString(cfVersion
).c_str());
138 trace
.add("version2", "%s", cfString(cfShortVersion
).c_str());
140 trace
.add("execname", "%s", cfString(cfExecutable
).c_str());
148 // Obtain special validation conditions for a static code, based on database configuration.
150 CFDictionaryRef
OpaqueWhitelist::validationConditionsFor(SecStaticCodeRef code
)
152 // figure out which team key to use
153 std::string team
= "UNKNOWN";
154 CFStringRef cfId
= NULL
;
155 CFStringRef cfVersion
= NULL
;
156 CFRef
<CFDictionaryRef
> info
; // holds lifetimes for the above
157 if (errSecSuccess
== SecCodeCopySigningInformation(code
, kSecCSSigningInformation
, &info
.aref())) {
158 if (CFStringRef cfTeam
= CFStringRef(CFDictionaryGetValue(info
, kSecCodeInfoTeamIdentifier
)))
159 team
= cfString(cfTeam
);
160 cfId
= CFStringRef(CFDictionaryGetValue(info
, kSecCodeInfoIdentifier
));
161 if (CFDictionaryRef infoPlist
= CFDictionaryRef(CFDictionaryGetValue(info
, kSecCodeInfoPList
)))
162 if (CFTypeRef version
= CFDictionaryGetValue(infoPlist
, _kCFBundleShortVersionStringKey
))
163 if (CFGetTypeID(version
) == CFStringGetTypeID())
164 cfVersion
= CFStringRef(version
);
166 if (cfId
== NULL
) // unsigned; punt
169 // find the highest weight matching condition. We perform no merging and the heaviest rule wins
170 SQLite::Statement
matches(*this,
171 "SELECT conditions FROM conditions"
172 " WHERE (source = :source or source IS NULL)"
173 " AND (identifier = :identifier or identifier is NULL)"
174 " AND ((:version IS NULL AND version IS NULL) OR (version = :version OR version IS NULL))"
175 " ORDER BY weight DESC"
178 matches
.bind(":source") = team
;
179 matches
.bind(":identifier") = cfString(cfId
);
181 matches
.bind(":version") = cfString(cfVersion
);
182 if (matches
.nextRow()) {
183 CFTemp
<CFDictionaryRef
> conditions((const char*)matches
[0]);
184 return conditions
.yield();
192 // Convert a SHA1 hash to a hex string
194 static std::string
hashString(CFDataRef hash
)
196 if (CFDataGetLength(hash
) != sizeof(SHA1::Digest
)) {
197 return std::string();
199 const UInt8
*bytes
= CFDataGetBytePtr(hash
);
200 char s
[2 * SHA1::digestLength
+ 1];
201 for (unsigned n
= 0; n
< SHA1::digestLength
; n
++)
202 sprintf(&s
[2*n
], "%2.2x", bytes
[n
]);
203 return std::string(s
);
209 // Add a code object to the whitelist
211 void OpaqueWhitelist::add(SecStaticCodeRef codeRef
)
213 // make our own copy of the code object
214 SecPointer
<SecStaticCode
> code
= new SecStaticCode(SecStaticCode::requiredStatic(codeRef
)->diskRep());
216 CFCopyRef
<CFDataRef
> current
= code
->cdHash();
217 attachOpaque(code
->handle(false), NULL
); // compute and attach an opaque signature
218 CFDataRef opaque
= code
->cdHash();
220 SQLite::Statement
insert(*this, "INSERT OR REPLACE INTO whitelist (current,opaque) VALUES (:current, :opaque)");
221 insert
.bind(":current") = current
.get();
222 insert
.bind(":opaque") = opaque
;
228 // Generate and attach an ad-hoc opaque signature
229 // Use SHA-1 digests because that's what the whitelist is made with
231 static void attachOpaque(SecStaticCodeRef code
, SecAssessmentFeedback feedback
)
233 CFTemp
<CFDictionaryRef
> rules("{" // same resource rules as used for collection
236 "'^Info\\.plist$' = {omit=#T,weight=10}"
238 "'^(Frameworks|SharedFrameworks|Plugins|Plug-ins|XPCServices|Helpers|MacOS)/' = {nested=#T, weight=0}"
240 "'^Info\\.plist$' = {omit=#T,weight=10}"
241 "'^[^/]+$' = {top=#T, weight=0}"
245 CFRef
<CFDataRef
> signature
= CFDataCreateMutable(NULL
, 0);
246 CFTemp
<CFDictionaryRef
> arguments("{%O=%O, %O=#N, %O=%d, %O=%O}",
247 kSecCodeSignerDetached
, signature
.get(),
248 kSecCodeSignerIdentity
, /* kCFNull, */
249 kSecCodeSignerDigestAlgorithm
, kSecCodeSignatureHashSHA1
,
250 kSecCodeSignerResourceRules
, rules
.get());
251 CFRef
<SecCodeSignerRef
> signer
;
252 SecCSFlags creationFlags
= kSecCSSignOpaque
| kSecCSSignNoV1
| kSecCSSignBundleRoot
;
253 SecCSFlags operationFlags
= 0;
256 operationFlags
|= kSecCSReportProgress
;
257 MacOSError::check(SecStaticCodeSetCallback(code
, kSecCSDefaultFlags
, NULL
, ^CFTypeRef(SecStaticCodeRef code
, CFStringRef stage
, CFDictionaryRef info
) {
258 if (CFEqual(stage
, CFSTR("progress"))) {
259 bool proceed
= feedback(kSecAssessmentFeedbackProgress
, info
);
261 SecStaticCodeCancelValidation(code
, kSecCSDefaultFlags
);
266 MacOSError::check(SecCodeSignerCreate(arguments
, creationFlags
, &signer
.aref()));
267 MacOSError::check(SecCodeSignerAddSignature(signer
, code
, operationFlags
));
268 MacOSError::check(SecCodeSetDetachedSignature(code
, signature
, kSecCSDefaultFlags
));
272 } // end namespace CodeSigning
273 } // end namespace Security