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 34namespace Security { 35namespace CodeSigning { 36 37using namespace SQLite; 38 39 40static void traceHash(MessageTrace &trace, const char *key, CFDataRef hash); 41static void attachOpaque(SecStaticCodeRef code); 42 43 44// 45// Open the database 46// 47OpaqueWhitelist::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 63OpaqueWhitelist::~OpaqueWhitelist() 64{ /* virtual */ } 65 66 67// 68// Check if a code object is whitelisted 69// 70bool 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// 142CFDictionaryRef 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// 186static 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// 203void 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// 222static 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 251