opaquewhitelist.cpp
1 /* 2 * Copyright (c) 2014-2016 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 #include <spawn.h> 34 35 namespace Security { 36 namespace CodeSigning { 37 38 using namespace SQLite; 39 40 41 static std::string hashString(CFDataRef hash); 42 static void attachOpaque(SecStaticCodeRef code, SecAssessmentFeedback feedback); 43 44 45 // 46 // Open the database 47 // 48 OpaqueWhitelist::OpaqueWhitelist(const char *path, int flags) 49 : SQLite::Database(path ? path : opaqueDatabase, flags) 50 { 51 SQLite::Statement createConditions(*this, 52 "CREATE TABLE IF NOT EXISTS conditions (" 53 " label text," 54 " weight real not null unique," 55 " source text," 56 " identifier text," 57 " version text," 58 " conditions text not null);" 59 ); 60 createConditions.execute(); 61 mOverrideQueue = dispatch_queue_create("com.apple.security.assessment.whitelist-override", DISPATCH_QUEUE_SERIAL); 62 } 63 64 OpaqueWhitelist::~OpaqueWhitelist() 65 { 66 dispatch_release(mOverrideQueue); 67 } 68 69 70 // 71 // Check if a code object is whitelisted 72 // 73 bool OpaqueWhitelist::contains(SecStaticCodeRef codeRef, SecAssessmentFeedback feedback, OSStatus reason) 74 { 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()); 77 78 CFCopyRef<CFDataRef> current = code->cdHash(); // current cdhash 79 CFDataRef opaque = NULL; // holds computed opaque cdhash 80 bool match = false; // holds final result 81 82 if (!current) 83 return false; // unsigned 84 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); 102 } 103 } 104 105 // compute and attach opaque signature 106 attachOpaque(code->handle(false), feedback); 107 opaque = code->cdHash(); 108 109 // lookup current cdhash in whitelist 110 if (opaque) { 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 118 break; 119 } 120 } 121 } 122 123 // prepare strings for use inside block 124 std::string currentHash = hashString(current); 125 std::string opaqueHash = opaque ? hashString(opaque) : "none"; 126 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); 133 if (!team.empty()) 134 trace.add("teamid", "%s", team.c_str()); 135 if (cfVersion) 136 trace.add("version", "%s", cfString(cfVersion).c_str()); 137 if (cfShortVersion) 138 trace.add("version2", "%s", cfString(cfShortVersion).c_str()); 139 if (cfExecutable) 140 trace.add("execname", "%s", cfString(cfExecutable).c_str()); 141 trace.send(""); 142 143 return match; 144 } 145 146 147 // 148 // Obtain special validation conditions for a static code, based on database configuration. 149 // 150 CFDictionaryRef OpaqueWhitelist::validationConditionsFor(SecStaticCodeRef code) 151 { 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); 165 } 166 if (cfId == NULL) // unsigned; punt 167 return NULL; 168 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" 176 " LIMIT 1" 177 ); 178 matches.bind(":source") = team; 179 matches.bind(":identifier") = cfString(cfId); 180 if (cfVersion) 181 matches.bind(":version") = cfString(cfVersion); 182 if (matches.nextRow()) { 183 CFTemp<CFDictionaryRef> conditions((const char*)matches[0]); 184 return conditions.yield(); 185 } 186 // no matches 187 return NULL; 188 } 189 190 191 // 192 // Convert a SHA1 hash to a hex string 193 // 194 static std::string hashString(CFDataRef hash) 195 { 196 if (CFDataGetLength(hash) != sizeof(SHA1::Digest)) { 197 return std::string(); 198 } else { 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); 204 } 205 } 206 207 208 // 209 // Add a code object to the whitelist 210 // 211 void OpaqueWhitelist::add(SecStaticCodeRef codeRef) 212 { 213 // make our own copy of the code object 214 SecPointer<SecStaticCode> code = new SecStaticCode(SecStaticCode::requiredStatic(codeRef)->diskRep()); 215 216 CFCopyRef<CFDataRef> current = code->cdHash(); 217 attachOpaque(code->handle(false), NULL); // compute and attach an opaque signature 218 CFDataRef opaque = code->cdHash(); 219 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; 223 insert.execute(); 224 } 225 226 227 // 228 // Generate and attach an ad-hoc opaque signature 229 // Use SHA-1 digests because that's what the whitelist is made with 230 // 231 static void attachOpaque(SecStaticCodeRef code, SecAssessmentFeedback feedback) 232 { 233 CFTemp<CFDictionaryRef> rules("{" // same resource rules as used for collection 234 "rules={" 235 "'^.*' = #T" 236 "'^Info\\.plist$' = {omit=#T,weight=10}" 237 "},rules2={" 238 "'^(Frameworks|SharedFrameworks|Plugins|Plug-ins|XPCServices|Helpers|MacOS)/' = {nested=#T, weight=0}" 239 "'^.*' = #T" 240 "'^Info\\.plist$' = {omit=#T,weight=10}" 241 "'^[^/]+$' = {top=#T, weight=0}" 242 "}" 243 "}"); 244 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; 254 255 if (feedback) 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); 260 if (!proceed) 261 SecStaticCodeCancelValidation(code, kSecCSDefaultFlags); 262 } 263 return NULL; 264 })); 265 266 MacOSError::check(SecCodeSignerCreate(arguments, creationFlags, &signer.aref())); 267 MacOSError::check(SecCodeSignerAddSignature(signer, code, operationFlags)); 268 MacOSError::check(SecCodeSetDetachedSignature(code, signature, kSecCSDefaultFlags)); 269 } 270 271 272 } // end namespace CodeSigning 273 } // end namespace Security