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// 46// Dump a signed object's signing data. 47// The more verbosity, the more data. 48// 49void dump(const char *target) 50{ 51 // get the code object (static or dynamic) 52 CFRef<SecStaticCodeRef> codeRef = dynamicCodePath(target); // dynamic input 53 if (!codeRef) 54 codeRef = staticCodePath(target, architecture, bundleVersion); 55 if (detached) { 56 if (CFRef<CFDataRef> dsig = cfLoadFile(detached)) 57 MacOSError::check(SecCodeSetDetachedSignature(codeRef, dsig, kSecCSDefaultFlags)); 58 else 59 fail("%s: cannot load detached signature", detached); 60 } 61 62 // get official (API driven) information 63 struct Info : public CFDictionary { 64 Info() : CFDictionary(errSecCSInternalError) { } 65 const std::string string(CFStringRef key) { return cfString(get<CFStringRef>(key)); } 66 const std::string url(CFStringRef key) { return cfString(get<CFURLRef>(key)); } 67 uint32_t number(CFStringRef key) { return cfNumber(get<CFNumberRef>(key)); } 68 using CFDictionary::get; // ... and all the others 69 }; 70 Info api; 71 SecCSFlags flags = kSecCSInternalInformation 72 | kSecCSSigningInformation 73 | kSecCSRequirementInformation 74 | kSecCSInternalInformation; 75 if (modifiedFiles) 76 flags |= kSecCSContentInformation; 77 MacOSError::check(SecCodeCopySigningInformation(codeRef, flags, &api.aref())); 78 79 // if the code is not signed, stop here 80 if (!api.get(kSecCodeInfoIdentifier)) 81 MacOSError::throwMe(errSecCSUnsigned); 82 83 // basic stuff 84 note(0, "Executable=%s", api.url(kSecCodeInfoMainExecutable).c_str()); 85 note(1, "Identifier=%s", api.string(kSecCodeInfoIdentifier).c_str()); 86 note(1, "Format=%s", api.string(kSecCodeInfoFormat).c_str()); 87 88 // code directory 89 using namespace CodeSigning; 90 const CodeDirectory *dir = 91 (const CodeDirectory *)CFDataGetBytePtr(api.get<CFDataRef>(kSecCodeInfoCodeDirectory)); 92 note(1, "CodeDirectory v=%x size=%d flags=%s hashes=%d+%d location=%s", 93 int(dir->version), dir->length(), flagForm(dir->flags).c_str(), 94 int(dir->nCodeSlots), int(dir->nSpecialSlots), 95 api.string(kSecCodeInfoSource).c_str()); 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 } else { 171 fprintf(stderr, "%s: no signature\n", target); 172 // but continue dumping 173 } 174 175 if (CFDictionaryRef info = api.get<CFDictionaryRef>(kSecCodeInfoPList)) 176 note(1, "Info.plist entries=%d", CFDictionaryGetCount(info)); 177 else 178 note(1, "Info.plist=not bound"); 179 180 if (CFStringRef teamID = api.get<CFStringRef>(kSecCodeInfoTeamIdentifier)) 181 note(1, "TeamIdentifier=%s", cfString(teamID).c_str()); 182 else 183 note(1, "TeamIdentifier=not set"); 184 185 186 if (CFDictionaryRef resources = api.get<CFDictionaryRef>(kSecCodeInfoResourceDirectory)) { 187 CFDictionaryRef rules1 = 188 CFDictionaryRef(CFDictionaryGetValue(resources, CFSTR("rules"))); 189 CFDictionaryRef rules2 = 190 CFDictionaryRef(CFDictionaryGetValue(resources, CFSTR("rules2"))); 191 CFDictionaryRef rules; 192 CFDictionaryRef files; 193 int version = 0; 194 if (rules2) { 195 rules = rules2; 196 files = CFDictionaryRef(CFDictionaryGetValue(resources, CFSTR("files2"))); 197 version = 2; 198 } else { 199 rules = rules1; 200 files = CFDictionaryRef(CFDictionaryGetValue(resources, CFSTR("files"))); 201 version = 1; 202 } 203 note(1, "Sealed Resources version=%d rules=%d files=%d", 204 version, CFDictionaryGetCount(rules), CFDictionaryGetCount(files)); 205 if (resourceRules) { 206 FILE *output; 207 if (!strcmp(resourceRules, "-")) { 208 output = stdout; 209 } else if (!(output = fopen(resourceRules, "w"))) { 210 perror(resourceRules); 211 exit(exitFailure); 212 } 213 CFRef<CFDictionaryRef> data = resources; 214 if (verbose <= 4) { 215 if (version > 1) 216 data = cfmake<CFDictionaryRef>("{rules=%O,rules2=%O}", rules, rules2); 217 else 218 data = cfmake<CFDictionaryRef>("{rules=%O}", rules); 219 } 220 CFRef<CFDataRef> rulesData = makeCFData(data.get()); 221 fwrite(CFDataGetBytePtr(rulesData), CFDataGetLength(rulesData), 1, output); 222 if (output != stdout) 223 fclose(output); 224 } 225 if (nested && version > 1) 226 CFDictionaryApplyFunction(files, listNestedCode, NULL); 227 } else 228 note(1, "Sealed Resources=none"); 229 230 CFDataRef ireqdata = api.get<CFDataRef>(kSecCodeInfoRequirementData); 231 CFRef<CFDictionaryRef> ireqset; 232 if (ireqdata) 233 MacOSError::check(SecRequirementsCopyRequirements(ireqdata, kSecCSDefaultFlags, &ireqset.aref())); 234 if (internalReq) { 235 FILE *output; 236 if (!strcmp(internalReq, "-")) { 237 output = stdout; 238 } else if (!(output = fopen(internalReq, "w"))) { 239 perror(internalReq); 240 exit(exitFailure); 241 } 242 if (CFStringRef ireqs = api.get<CFStringRef>(kSecCodeInfoRequirements)) 243 fprintf(output, "%s", cfString(ireqs).c_str()); 244 if (!(ireqset && CFDictionaryContainsKey(ireqset, CFTempNumber(uint32_t(kSecDesignatedRequirementType))))) { // no explicit DR 245 CFRef<SecRequirementRef> dr; 246 MacOSError::check(SecCodeCopyDesignatedRequirement(codeRef, kSecCSDefaultFlags, &dr.aref())); 247 CFRef<CFStringRef> drstring; 248 MacOSError::check(SecRequirementCopyString(dr, kSecCSDefaultFlags, &drstring.aref())); 249 fprintf(output, "# designated => %s\n", cfString(drstring).c_str()); 250 } 251 if (output != stdout) 252 fclose(output); 253 } else { 254 if (ireqdata) { 255 note(1, "Internal requirements count=%d size=%d", 256 CFDictionaryGetCount(ireqset), CFDataGetLength(ireqdata)); 257 } else 258 note(1, "Internal requirements=none"); 259 } 260 261 if (entitlements) { 262 CFCopyRef<CFDataRef> data = CFDataRef(CFDictionaryGetValue(api, kSecCodeInfoEntitlements)); 263 // a destination prefixed with ':' means strip the binary header off the data 264 if (entitlements[0] == ':') { 265 static const unsigned headerSize = sizeof(BlobCore); 266 if (data) 267 data = CFDataCreateWithBytesNoCopy(NULL, 268 CFDataGetBytePtr(data) + headerSize, CFDataGetLength(data) - headerSize, kCFAllocatorNull); 269 entitlements++; 270 } 271 writeData(data, entitlements, "a"); 272 } 273 274 if (modifiedFiles) 275 writeFileList(CFArrayRef(CFDictionaryGetValue(api, kSecCodeInfoChangedFiles)), modifiedFiles, "a"); 276} 277 278 279// 280// Show details of resource seal entries 281// 282static void listNestedCode(const void *key, const void *value, void *context) 283{ 284 if (value && CFGetTypeID(value) == CFDictionaryGetTypeID()) { 285 CFDictionary seal(value, noErr); 286 if (seal && CFDictionaryGetValue(seal, CFSTR("requirement"))) 287 note(0, "Nested=%s", cfString(CFStringRef(key)).c_str()); 288 } 289} 290 291 292// 293// Extract the entire embedded certificate chain from a signature. 294// This generates DER-form certificate files, one cert per file, named 295// prefix_n (where prefix is specified by the caller). 296// 297void extractCertificates(const char *prefix, CFArrayRef certChain) 298{ 299 CFIndex count = CFArrayGetCount(certChain); 300 for (CFIndex n = 0; n < count; n++) { 301 SecCertificateRef cert = SecCertificateRef(CFArrayGetValueAtIndex(certChain, n)); 302 CSSM_DATA certData; 303 MacOSError::check(SecCertificateGetData(cert, &certData)); 304 char name[PATH_MAX]; 305 snprintf(name, sizeof(name), "%s%ld", prefix, n); 306 AutoFileDesc(name, O_WRONLY | O_CREAT | O_TRUNC).writeAll(certData.Data, certData.Length); 307 } 308} 309 310 311string flagForm(uint32_t flags) 312{ 313 if (flags == 0) 314 return "0x0(none)"; 315 316 string r; 317 uint32_t leftover = flags; 318 for (const SecCodeDirectoryFlagTable *item = kSecCodeDirectoryFlagTable; item->name; item++) 319 if (flags & item->value) { 320 r = r + "," + item->name; 321 leftover &= ~item->value; 322 } 323 if (leftover) 324 r += ",???"; 325 char buf[80]; 326 snprintf(buf, sizeof(buf), "0x%x", flags); 327 return string(buf) + "(" + r.substr(1) + ")"; 328} 329