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>
35 namespace CodeSigning
{
37 using namespace SQLite
;
40 static void traceHash(MessageTrace
&trace
, const char *key
, CFDataRef hash
);
41 static void attachOpaque(SecStaticCodeRef code
);
47 OpaqueWhitelist::OpaqueWhitelist(const char *path
, int flags
)
48 : SQLite::Database(path
? path
: opaqueDatabase
, flags
)
50 SQLite::Statement
createConditions(*this,
51 "CREATE TABLE IF NOT EXISTS conditions ("
53 " weight real not null unique,"
57 " conditions text not null);"
59 createConditions
.execute();
63 OpaqueWhitelist::~OpaqueWhitelist()
68 // Check if a code object is whitelisted
70 bool OpaqueWhitelist::contains(SecStaticCodeRef codeRef
, OSStatus reason
, bool trace
)
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());
75 CFCopyRef
<CFDataRef
> current
= code
->cdHash(); // current cdhash
76 CFDataRef opaque
= NULL
; // holds computed opaque cdhash
77 bool match
= false; // holds final result
80 return false; // unsigned
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
);
102 // compute and attach opaque signature
103 attachOpaque(code
->handle(false));
104 opaque
= code
->cdHash();
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
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
);
125 trace
.add("teamid", "%s", team
.c_str());
127 trace
.add("version", "%s", cfString(cfVersion
).c_str());
129 trace
.add("version2", "%s", cfString(cfShortVersion
).c_str());
131 trace
.add("execname", "%s", cfString(cfExecutable
).c_str());
140 // Obtain special validation conditions for a static code, based on database configuration.
142 CFDictionaryRef
OpaqueWhitelist::validationConditionsFor(SecStaticCodeRef code
)
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
);
158 if (cfId
== NULL
) // unsigned; punt
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"
170 matches
.bind(":source") = team
;
171 matches
.bind(":identifier") = cfString(cfId
);
173 matches
.bind(":version") = cfString(cfVersion
);
174 if (matches
.nextRow()) {
175 CFTemp
<CFDictionaryRef
> conditions((const char*)matches
[0]);
176 return conditions
.yield();
184 // Convert a SHA1 hash to hex and add to a trace
186 static void traceHash(MessageTrace
&trace
, const char *key
, CFDataRef hash
)
188 if (CFDataGetLength(hash
) != sizeof(SHA1::Digest
)) {
189 trace
.add(key
, "(unknown format)");
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
]);
201 // Add a code object to the whitelist
203 void OpaqueWhitelist::add(SecStaticCodeRef codeRef
)
205 // make our own copy of the code object
206 SecPointer
<SecStaticCode
> code
= new SecStaticCode(SecStaticCode::requiredStatic(codeRef
)->diskRep());
208 CFCopyRef
<CFDataRef
> current
= code
->cdHash();
209 attachOpaque(code
->handle(false)); // compute and attach an opaque signature
210 CFDataRef opaque
= code
->cdHash();
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
;
220 // Generate and attach an ad-hoc opaque signature
222 static void attachOpaque(SecStaticCodeRef code
)
224 CFTemp
<CFDictionaryRef
> rules("{" // same resource rules as used for collection
227 "'^Info\\.plist$' = {omit=#T,weight=10}"
229 "'^(Frameworks|SharedFrameworks|Plugins|Plug-ins|XPCServices|Helpers|MacOS)/' = {nested=#T, weight=0}"
231 "'^Info\\.plist$' = {omit=#T,weight=10}"
232 "'^[^/]+$' = {top=#T, weight=0}"
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
));
249 } // end namespace CodeSigning
250 } // end namespace Security