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
);
125 // send a trace indicating the result
126 MessageTrace
trace("com.apple.security.assessment.whitelist2", code
->identifier().c_str());
127 trace
.add("signature2", "%s", currentHash
.c_str());
128 trace
.add("signature3", "%s", opaqueHash
.c_str());
129 trace
.add("result", match
? "pass" : "fail");
130 trace
.add("reason", "%d", reason
);
132 trace
.add("teamid", "%s", team
.c_str());
134 trace
.add("version", "%s", cfString(cfVersion
).c_str());
136 trace
.add("version2", "%s", cfString(cfShortVersion
).c_str());
138 trace
.add("execname", "%s", cfString(cfExecutable
).c_str());
146 // Obtain special validation conditions for a static code, based on database configuration.
148 CFDictionaryRef
OpaqueWhitelist::validationConditionsFor(SecStaticCodeRef code
)
150 // figure out which team key to use
151 std::string team
= "UNKNOWN";
152 CFStringRef cfId
= NULL
;
153 CFStringRef cfVersion
= NULL
;
154 CFRef
<CFDictionaryRef
> info
; // holds lifetimes for the above
155 if (errSecSuccess
== SecCodeCopySigningInformation(code
, kSecCSSigningInformation
, &info
.aref())) {
156 if (CFStringRef cfTeam
= CFStringRef(CFDictionaryGetValue(info
, kSecCodeInfoTeamIdentifier
)))
157 team
= cfString(cfTeam
);
158 cfId
= CFStringRef(CFDictionaryGetValue(info
, kSecCodeInfoIdentifier
));
159 if (CFDictionaryRef infoPlist
= CFDictionaryRef(CFDictionaryGetValue(info
, kSecCodeInfoPList
)))
160 if (CFTypeRef version
= CFDictionaryGetValue(infoPlist
, _kCFBundleShortVersionStringKey
))
161 if (CFGetTypeID(version
) == CFStringGetTypeID())
162 cfVersion
= CFStringRef(version
);
164 if (cfId
== NULL
) // unsigned; punt
167 // find the highest weight matching condition. We perform no merging and the heaviest rule wins
168 SQLite::Statement
matches(*this,
169 "SELECT conditions FROM conditions"
170 " WHERE (source = :source or source IS NULL)"
171 " AND (identifier = :identifier or identifier is NULL)"
172 " AND ((:version IS NULL AND version IS NULL) OR (version = :version OR version IS NULL))"
173 " ORDER BY weight DESC"
176 matches
.bind(":source") = team
;
177 matches
.bind(":identifier") = cfString(cfId
);
179 matches
.bind(":version") = cfString(cfVersion
);
180 if (matches
.nextRow()) {
181 CFTemp
<CFDictionaryRef
> conditions((const char*)matches
[0]);
182 return conditions
.yield();
190 // Convert a SHA1 hash to a hex string
192 static std::string
hashString(CFDataRef hash
)
194 if (CFDataGetLength(hash
) != sizeof(SHA1::Digest
)) {
195 return std::string();
197 const UInt8
*bytes
= CFDataGetBytePtr(hash
);
198 char s
[2 * SHA1::digestLength
+ 1];
199 for (unsigned n
= 0; n
< SHA1::digestLength
; n
++)
200 sprintf(&s
[2*n
], "%2.2x", bytes
[n
]);
201 return std::string(s
);
207 // Add a code object to the whitelist
209 void OpaqueWhitelist::add(SecStaticCodeRef codeRef
)
211 // make our own copy of the code object
212 SecPointer
<SecStaticCode
> code
= new SecStaticCode(SecStaticCode::requiredStatic(codeRef
)->diskRep());
214 CFCopyRef
<CFDataRef
> current
= code
->cdHash();
215 attachOpaque(code
->handle(false), NULL
); // compute and attach an opaque signature
216 CFDataRef opaque
= code
->cdHash();
218 SQLite::Statement
insert(*this, "INSERT OR REPLACE INTO whitelist (current,opaque) VALUES (:current, :opaque)");
219 insert
.bind(":current") = current
.get();
220 insert
.bind(":opaque") = opaque
;
226 // Generate and attach an ad-hoc opaque signature
228 static void attachOpaque(SecStaticCodeRef code
, SecAssessmentFeedback feedback
)
230 CFTemp
<CFDictionaryRef
> rules("{" // same resource rules as used for collection
233 "'^Info\\.plist$' = {omit=#T,weight=10}"
235 "'^(Frameworks|SharedFrameworks|Plugins|Plug-ins|XPCServices|Helpers|MacOS)/' = {nested=#T, weight=0}"
237 "'^Info\\.plist$' = {omit=#T,weight=10}"
238 "'^[^/]+$' = {top=#T, weight=0}"
242 CFRef
<CFDataRef
> signature
= CFDataCreateMutable(NULL
, 0);
243 CFTemp
<CFDictionaryRef
> arguments("{%O=%O, %O=#N, %O=%O}",
244 kSecCodeSignerDetached
, signature
.get(),
245 kSecCodeSignerIdentity
, /* kCFNull, */
246 kSecCodeSignerResourceRules
, rules
.get());
247 CFRef
<SecCodeSignerRef
> signer
;
248 SecCSFlags creationFlags
= kSecCSSignOpaque
| kSecCSSignNoV1
| kSecCSSignBundleRoot
;
249 SecCSFlags operationFlags
= 0;
252 operationFlags
|= kSecCSReportProgress
;
253 MacOSError::check(SecStaticCodeSetCallback(code
, kSecCSDefaultFlags
, NULL
, ^CFTypeRef(SecStaticCodeRef code
, CFStringRef stage
, CFDictionaryRef info
) {
254 if (CFEqual(stage
, CFSTR("progress"))) {
255 bool proceed
= feedback(kSecAssessmentFeedbackProgress
, info
);
257 SecStaticCodeCancelValidation(code
, kSecCSDefaultFlags
);
262 MacOSError::check(SecCodeSignerCreate(arguments
, creationFlags
, &signer
.aref()));
263 MacOSError::check(SecCodeSignerAddSignature(signer
, code
, operationFlags
));
264 MacOSError::check(SecCodeSetDetachedSignature(code
, signature
, kSecCSDefaultFlags
));
268 } // end namespace CodeSigning
269 } // end namespace Security