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