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