1/* 2 * Copyright (c) 2006-2010 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 24// 25// cs_dump - codesign dump/display operations 26// 27#include "codesign.h" 28#include <Security/CSCommonPriv.h> 29#include <Security/SecCodePriv.h> 30#include <security_utilities/cfmunge.h> 31#include <security_codesigning/codedirectory.h> // strictly for the data format 32#include <cmath> 33 34using namespace UnixPlusPlus; 35 36 37// 38// Local functions 39// 40static void extractCertificates(const char *prefix, CFArrayRef certChain); 41static void listNestedCode(const void *key, const void *value, void *context); 42static string flagForm(uint32_t flags); 43 44// 45// Dump a signed object's signing data. 46// The more verbosity, the more data. 47// 48void dump(const char *target) 49{ 50 // get the code object (static or dynamic) 51 CFRef<SecStaticCodeRef> codeRef = dynamicCodePath(target); // dynamic input 52 if (!codeRef) 53 codeRef = staticCodePath(target, architecture, bundleVersion); 54 if (detached) { 55 if (CFRef<CFDataRef> dsig = cfLoadFile(detached)) 56 MacOSError::check(SecCodeSetDetachedSignature(codeRef, dsig, kSecCSDefaultFlags)); 57 else 58 fail("%s: cannot load detached signature", detached); 59 } 60 61 // get official (API driven) information 62 struct Info : public CFDictionary { 63 Info() : CFDictionary(errSecCSInternalError) { } 64 const std::string string(CFStringRef key) { return cfString(get<CFStringRef>(key)); } 65 const std::string url(CFStringRef key) { return cfString(get<CFURLRef>(key)); } 66 uint32_t number(CFStringRef key) { return cfNumber(get<CFNumberRef>(key)); } 67 using CFDictionary::get; // ... and all the others 68 }; 69 Info api; 70 SecCSFlags flags = kSecCSInternalInformation 71 | kSecCSSigningInformation 72 | kSecCSRequirementInformation 73 | kSecCSInternalInformation; 74 if (modifiedFiles) 75 flags |= kSecCSContentInformation; 76 MacOSError::check(SecCodeCopySigningInformation(codeRef, flags, &api.aref())); 77 78 // if the code is not signed, stop here 79 if (!api.get(kSecCodeInfoIdentifier)) 80 MacOSError::throwMe(errSecCSUnsigned); 81 82 // basic stuff 83 note(0, "Executable=%s", api.url(kSecCodeInfoMainExecutable).c_str()); 84 note(1, "Identifier=%s", api.string(kSecCodeInfoIdentifier).c_str()); 85 note(1, "Format=%s", api.string(kSecCodeInfoFormat).c_str()); 86 87 // code directory 88 using namespace CodeSigning; 89 const CodeDirectory *dir = 90 (const CodeDirectory *)CFDataGetBytePtr(api.get<CFDataRef>(kSecCodeInfoCodeDirectory)); 91 note(1, "CodeDirectory v=%x size=%d flags=%s hashes=%d+%d location=%s", 92 int(dir->version), dir->length(), flagForm(dir->flags).c_str(), 93 int(dir->nCodeSlots), int(dir->nSpecialSlots), 94 api.string(kSecCodeInfoSource).c_str()); 95 96 if (verbose > 2) { 97 uint32_t hashType = api.number(kSecCodeInfoDigestAlgorithm); 98 if (const HashType *type = findHashType(hashType)) 99 note(3, "Hash type=%s size=%d", type->name, type->size); 100 else 101 note(3, "Hash UNKNOWN type=%d", hashType); 102 } 103 if (verbose > 4) { 104 typedef CodeDirectory::Slot Slot; 105 Slot end = (verbose > 5) ? Slot(dir->nCodeSlots) : -1; 106 for (Slot slot = -dir->nSpecialSlots; slot < end; slot++) 107 note(5, "%6d=%s", slot, hashString((*dir)[slot]).c_str()); 108 } 109 110 if (const CodeDirectory::Scatter *scatter = dir->scatterVector()) { 111 const CodeDirectory::Scatter *end = scatter; 112 while (end->count) end++; 113 note(1, "ScatterVector count=%d", int(end - scatter)); 114 for (const CodeDirectory::Scatter *s = scatter; s < end; s++) 115 note(3, "Scatter i=%u count=%u base=%u offset=0x%llx", 116 unsigned(s - scatter), unsigned(s->count), unsigned(s->base), uint64_t(s->targetOffset)); 117 } 118 119 if (verbose > 2) 120 if (CFTypeRef hashInfo = api.get(kSecCodeInfoUnique)) { 121 CFDataRef hash = CFDataRef(hashInfo); 122 if (CFDataGetLength(hash) != sizeof(SHA1::Digest)) 123 note(3, "CDHash=(unknown format)"); 124 else 125 note(3, "CDHash=%s", hashString(CFDataGetBytePtr(hash)).c_str()); 126 } 127 128 // signature 129 if (dir->flags & kSecCodeSignatureAdhoc) { 130 note(1, "Signature=adhoc"); 131 } else if (CFDataRef signature = api.get<CFDataRef>(kSecCodeInfoCMS)) { 132 note(1, "Signature size=%d", CFDataGetLength(signature)); 133 CFArrayRef certChain = api.get<CFArrayRef>(kSecCodeInfoCertificates); 134 if (verbose > 1) { 135 // dump cert chain 136 CFIndex count = CFArrayGetCount(certChain); 137 for (CFIndex n = 0; n < count; n++) { 138 SecCertificateRef cert = SecCertificateRef(CFArrayGetValueAtIndex(certChain, n)); 139 CFRef<CFStringRef> commonName; 140 MacOSError::check(SecCertificateCopyCommonName(cert, &commonName.aref())); 141 note(2, "Authority=%s", cfString(commonName).c_str()); 142 } 143 } 144 if (extractCerts) 145 extractCertificates(extractCerts, certChain); 146 147 CFRef<CFLocaleRef> userLocale = CFLocaleCopyCurrent(); 148 CFRef<CFDateFormatterRef> format = CFDateFormatterCreate(NULL, userLocale, 149 kCFDateFormatterMediumStyle, kCFDateFormatterMediumStyle); 150 CFDateRef softTime = CFDateRef(CFDictionaryGetValue(api, kSecCodeInfoTime)); 151 CFDateRef hardTime = CFDateRef(CFDictionaryGetValue(api, kSecCodeInfoTimestamp)); 152 if (hardTime) { 153 CFRef<CFStringRef> s = CFDateFormatterCreateStringWithDate(NULL, format, hardTime); 154 note(1, "Timestamp=%s", cfString(s).c_str()); 155 if (CFAbsoluteTimeGetCurrent() < CFDateGetAbsoluteTime(hardTime) - timestampSlop) 156 fail("%s: postdated timestamp or bad system clock", target); 157 if (softTime) { 158 CFAbsoluteTime slop = abs(CFDateGetAbsoluteTime(softTime) - CFDateGetAbsoluteTime(hardTime)); 159 if (slop > timestampSlop) { 160 CFRef<CFStringRef> s = CFDateFormatterCreateStringWithDate(NULL, format, softTime); 161 note(0, "%s: timestamp mismatch: internal time %s (%g seconds apart)", target, cfString(s).c_str(), slop); 162 } 163 } 164 } else if (softTime) { 165 CFRef<CFStringRef> s = CFDateFormatterCreateStringWithDate(NULL, format, softTime); 166 note(1, "Signed Time=%s", cfString(s).c_str()); 167 if (CFAbsoluteTimeGetCurrent() < CFDateGetAbsoluteTime(softTime) - timestampSlop) 168 note(0, "%s: postdated signing date or bad system clock", target); 169 } 170 171 /** Dump the code directory to disk as a binary. Do this last so that if there 172 is a problem writing/creating the file, the rest of it is still printed */ 173 if (dumpBinary) 174 AutoFileDesc(dumpBinary, O_WRONLY|O_CREAT|O_TRUNC, 0666).writeAll(dir, dir->length()); 175 } else { 176 fprintf(stderr, "%s: no signature\n", target); 177 // but continue dumping 178 } 179 180 if (CFDictionaryRef info = api.get<CFDictionaryRef>(kSecCodeInfoPList)) 181 note(1, "Info.plist entries=%d", CFDictionaryGetCount(info)); 182 else 183 note(1, "Info.plist=not bound"); 184 185 if (CFStringRef teamID = api.get<CFStringRef>(kSecCodeInfoTeamIdentifier)) 186 note(1, "TeamIdentifier=%s", cfString(teamID).c_str()); 187 else 188 note(1, "TeamIdentifier=not set"); 189 190 191 if (CFDictionaryRef resources = api.get<CFDictionaryRef>(kSecCodeInfoResourceDirectory)) { 192 CFDictionaryRef rules1 = 193 CFDictionaryRef(CFDictionaryGetValue(resources, CFSTR("rules"))); 194 CFDictionaryRef rules2 = 195 CFDictionaryRef(CFDictionaryGetValue(resources, CFSTR("rules2"))); 196 CFDictionaryRef rules; 197 CFDictionaryRef files; 198 int version = 0; 199 if (rules2) { 200 rules = rules2; 201 files = CFDictionaryRef(CFDictionaryGetValue(resources, CFSTR("files2"))); 202 version = 2; 203 } else { 204 rules = rules1; 205 files = CFDictionaryRef(CFDictionaryGetValue(resources, CFSTR("files"))); 206 version = 1; 207 } 208 note(1, "Sealed Resources version=%d rules=%d files=%d", 209 version, CFDictionaryGetCount(rules), CFDictionaryGetCount(files)); 210 if (resourceRules) { 211 FILE *output; 212 if (!strcmp(resourceRules, "-")) { 213 output = stdout; 214 } else if (!(output = fopen(resourceRules, "w"))) { 215 perror(resourceRules); 216 exit(exitFailure); 217 } 218 CFCopyRef<CFDictionaryRef> data = resources; 219 if (verbose <= 4) { 220 if (version > 1) 221 data = cfmake<CFDictionaryRef>("{rules=%O,rules2=%O}", rules, rules2); 222 else 223 data = cfmake<CFDictionaryRef>("{rules=%O}", rules); 224 } 225 CFRef<CFDataRef> rulesData = makeCFData(data.get()); 226 fwrite(CFDataGetBytePtr(rulesData), CFDataGetLength(rulesData), 1, output); 227 if (output != stdout) 228 fclose(output); 229 } 230 if (nested && version > 1) 231 CFDictionaryApplyFunction(files, listNestedCode, NULL); 232 } else 233 note(1, "Sealed Resources=none"); 234 235 CFDataRef ireqdata = api.get<CFDataRef>(kSecCodeInfoRequirementData); 236 CFRef<CFDictionaryRef> ireqset; 237 if (ireqdata) 238 MacOSError::check(SecRequirementsCopyRequirements(ireqdata, kSecCSDefaultFlags, &ireqset.aref())); 239 if (internalReq) { 240 FILE *output; 241 if (!strcmp(internalReq, "-")) { 242 output = stdout; 243 } else if (!(output = fopen(internalReq, "w"))) { 244 perror(internalReq); 245 exit(exitFailure); 246 } 247 if (CFStringRef ireqs = api.get<CFStringRef>(kSecCodeInfoRequirements)) 248 fprintf(output, "%s", cfString(ireqs).c_str()); 249 if (!(ireqset && CFDictionaryContainsKey(ireqset, CFTempNumber(uint32_t(kSecDesignatedRequirementType))))) { // no explicit DR 250 CFRef<SecRequirementRef> dr; 251 MacOSError::check(SecCodeCopyDesignatedRequirement(codeRef, kSecCSDefaultFlags, &dr.aref())); 252 CFRef<CFStringRef> drstring; 253 MacOSError::check(SecRequirementCopyString(dr, kSecCSDefaultFlags, &drstring.aref())); 254 fprintf(output, "# designated => %s\n", cfString(drstring).c_str()); 255 } 256 if (output != stdout) 257 fclose(output); 258 } else { 259 if (ireqdata) { 260 note(1, "Internal requirements count=%d size=%d", 261 CFDictionaryGetCount(ireqset), CFDataGetLength(ireqdata)); 262 } else 263 note(1, "Internal requirements=none"); 264 } 265 266 if (entitlements) { 267 CFCopyRef<CFDataRef> data = CFDataRef(CFDictionaryGetValue(api, kSecCodeInfoEntitlements)); 268 // a destination prefixed with ':' means strip the binary header off the data 269 if (entitlements[0] == ':') { 270 static const unsigned headerSize = sizeof(BlobCore); 271 if (data) 272 data = CFDataCreateWithBytesNoCopy(NULL, 273 CFDataGetBytePtr(data) + headerSize, CFDataGetLength(data) - headerSize, kCFAllocatorNull); 274 entitlements++; 275 } 276 writeData(data, entitlements, "a"); 277 } 278 279 if (modifiedFiles) 280 writeFileList(CFArrayRef(CFDictionaryGetValue(api, kSecCodeInfoChangedFiles)), modifiedFiles, "a"); 281} 282 283 284// 285// Show details of resource seal entries 286// 287static void listNestedCode(const void *key, const void *value, void *context) 288{ 289 if (value && CFGetTypeID(value) == CFDictionaryGetTypeID()) { 290 CFDictionary seal(value, noErr); 291 if (seal && CFDictionaryGetValue(seal, CFSTR("requirement"))) 292 note(0, "Nested=%s", cfString(CFStringRef(key)).c_str()); 293 } 294} 295 296 297// 298// Extract the entire embedded certificate chain from a signature. 299// This generates DER-form certificate files, one cert per file, named 300// prefix_n (where prefix is specified by the caller). 301// 302void extractCertificates(const char *prefix, CFArrayRef certChain) 303{ 304 CFIndex count = CFArrayGetCount(certChain); 305 for (CFIndex n = 0; n < count; n++) { 306 SecCertificateRef cert = SecCertificateRef(CFArrayGetValueAtIndex(certChain, n)); 307 CSSM_DATA certData; 308 MacOSError::check(SecCertificateGetData(cert, &certData)); 309 char name[PATH_MAX]; 310 snprintf(name, sizeof(name), "%s%ld", prefix, n); 311 AutoFileDesc(name, O_WRONLY | O_CREAT | O_TRUNC).writeAll(certData.Data, certData.Length); 312 } 313} 314 315 316string flagForm(uint32_t flags) 317{ 318 if (flags == 0) 319 return "0x0(none)"; 320 321 string r; 322 uint32_t leftover = flags; 323 for (const SecCodeDirectoryFlagTable *item = kSecCodeDirectoryFlagTable; item->name; item++) 324 if (flags & item->value) { 325 r = r + "," + item->name; 326 leftover &= ~item->value; 327 } 328 if (leftover) 329 r += ",???"; 330 char buf[80]; 331 snprintf(buf, sizeof(buf), "0x%x", flags); 332 return string(buf) + "(" + r.substr(1) + ")"; 333} 334