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 operation
26//
27#include "codesign.h"
28#include <Security/SecKeychain.h>
29#include "cs_utils.h"
30#include <cmath>
31#include <getopt.h>
32
33using namespace UnixPlusPlus;
34
35
36//
37// Operational mode
38//
39enum Operation {
40	doNothing,						// none given (print usage)
41	doSign,							// sign code
42	doVerify,						// verify code
43	doDump,							// dump/display signature
44	doHostingInfo,					// build and display hosting chain
45	doProcInfo,						// process state information
46	doProcAction					// process state manipulation
47};
48Operation operation = doNothing;
49
50
51//
52// Command-line arguments and options
53//
54size_t pagesize = pagesizeUnspecified; // signing page size (-1 => not specified)
55SecIdentityRef signer = NULL;		// signer identity
56SecKeychainRef keychain = NULL;		// source keychain for signer identity
57const char *internalReq = NULL;		// internal requirement (raw optarg)
58const char *testReq = NULL;			// external requirement (raw optarg)
59const char *detached = NULL;		// detached signature path (to explicit file)
60const char *detachedDb = NULL;		// reference to detached signature database
61const char *entitlements = NULL;	// path to entitlement configuration input
62const char *resourceRules = NULL;	// explicit resource rules template
63const char *uniqueIdentifier = NULL; // unique ident hash
64const char *identifierPrefix = NULL; // prefix for un-dotted default identifiers
65const char *teamID = NULL;          // TeamID
66const char *modifiedFiles = NULL;	// file to receive list of modified files
67const char *extractCerts = NULL;	// location for extracting signing chain certificates
68const char *sdkRoot = NULL;			// alternate root for looking up sub-components
69const char *featureCheck = NULL;	// feature support check
70SecCSFlags staticVerifyOptions = kSecCSCheckAllArchitectures | kSecCSStrictValidate; // option flags to static verifications
71SecCSFlags dynamicVerifyOptions = kSecCSDefaultFlags; // option flags to dynamic verifications
72SecCSFlags signOptions = kSecCSSignStrictPreflight; // option flags to signing operations
73uint32_t digestAlgorithm = 0;		// digest algorithm to be used when signing
74CFDateRef signingTime;				// explicit signing time option
75size_t signatureSize = 0;			// override CMS blob estimate
76uint32_t cdFlags = 0;				// CodeDirectory flags requested
77const char *procAction = NULL;		// action-on-process(es) requested
78Architecture architecture;			// specific binary architecture to process (from a universal file)
79const char *bundleVersion;			// specific version string requested (from a versioned bundle)
80bool noMachO = false;				// force non-MachO operation
81bool dryrun = false;				// do not actually change anything
82bool allArchitectures = false;		// process all architectures in a universal (aka fat) code file
83bool nested = false;				// nested code processing (--deep)
84uint32_t preserveMetadata = 0;		// what metadata to keep from previous signature
85CFBooleanRef timestampRequest = NULL; // timestamp service request
86bool noTSAcerts = false;			// Don't request certificates with ts request
87const char *tsaURL = NULL;			// TimeStamping Authority URL
88const char *dumpBinary = NULL;		// dump a binary image of the CodeDirectory to disk
89
90
91//
92// Feature set
93//
94static const char *features[] = {
95	"hash-identities",				// supports -s hash-of-certificate
96	"identity-preferences",		// supports -s identity-preference-name
97	"deep-verify",					// supports --deep-verify
98	"numeric-errors",				// supports --numeric-errors
99	"deep-signing",					// supports "deep" (recursive) signing
100	NULL							// sentinel
101};
102
103
104//
105// Local functions
106//
107static void usage();
108static OSStatus keychain_open(const char *name, SecKeychainRef &keychain);
109static void chooseArchitecture(const char *arg);
110static uint32_t parseMetadataFlags(const char *arg);
111static void checkFeatures(const char *arg);
112static bool checkTeamId(const char *teamID);
113
114static const int TEAM_ID_MAX = 100;
115
116
117//
118// Command-line options
119//
120enum {
121	optNone = 0,	// null (no, absent) option
122	optAllArchitectures,
123	optBundleVersion,
124	optCheckExpiration,
125	optCheckRevocation,
126	optCodeDirectory,
127	optContinue,
128	optDeep,
129	optDetachedDatabase,
130	optDigestAlgorithm,
131	optDryRun,
132	optExtractCerts,
133	optEntitlements,
134	optFeatures,
135	optFMJ,
136	optFileList,
137	optIdentifierPrefix,
138	optIgnoreResources,
139	optKeychain,
140	optLegacy,
141	optNoLegacy,
142	optNoMachO,
143	optNumeric,
144	optPreserveMetadata,
145	optProcInfo,
146	optProcAction,
147	optRemoveSignature,
148	optResourceRules,
149	optSDKRoot,
150	optSigningTime,
151	optSignatureSize,
152    optTimestamp,
153    optTSANoCerts,
154    optTeamID,
155    optNoStrict,
156    optSealRoot,
157};
158
159const struct option options[] = {
160	{ "architecture", required_argument,	NULL, 'a' },
161	{ "dump",		no_argument,			NULL, 'd' },
162	{ "display",	no_argument,			NULL, 'd' },
163	{ "detached",	required_argument,		NULL, 'D' },
164	{ "force",		no_argument,			NULL, 'f' },
165	{ "help",		no_argument,			NULL, '?' },
166	{ "hosting",	no_argument,			NULL, 'h' },
167	{ "identifier",	required_argument,		NULL, 'i' },
168	{ "options",	required_argument,		NULL, 'o' },
169	{ "pagesize",	required_argument,		NULL, 'P' },
170	{ "requirements", required_argument,	NULL, 'r' },
171	{ "test-requirement", required_argument,NULL, 'R' },
172	{ "sign",		required_argument,		NULL, 's' },
173	{ "verbose",	optional_argument,		NULL, 'v' },
174	{ "verify",		no_argument,			NULL, 'v' },
175
176	{ "all-architectures", no_argument,		NULL, optAllArchitectures },
177	{ "bundle-version", required_argument,	NULL, optBundleVersion },
178	{ "check-expiration", no_argument,		NULL, optCheckExpiration },
179	{ "check-revocation", no_argument,		NULL, optCheckRevocation },
180	{ "codedirectory", required_argument,		NULL, optCodeDirectory },
181	{ "continue",	no_argument,			NULL, optContinue },
182	{ "deep",		no_argument,			NULL, optDeep },
183	{ "deep-verify", no_argument,			NULL, optDeep },	// legacy
184	{ "detached-database", optional_argument, NULL, optDetachedDatabase },
185	{ "digest-algorithm", required_argument, NULL, optDigestAlgorithm },
186	{ "dryrun",		no_argument,			NULL, optDryRun },
187	{ "entitlements", required_argument,	NULL, optEntitlements },
188	{ "expired",	no_argument,			NULL, optCheckExpiration },
189	{ "extract-certificates", optional_argument, NULL, optExtractCerts },
190	{ "features",	optional_argument,		NULL, optFeatures },
191	{ "file-list",	required_argument,		NULL, optFileList },
192	{ "full-metal-jacket", no_argument,		NULL, optFMJ },
193	{ "ignore-resources", no_argument,		NULL, optIgnoreResources },
194	{ "keychain",	required_argument,		NULL, optKeychain },
195	{ "legacy-signing", no_argument,		NULL, optLegacy },
196	{ "no-legacy-signing", no_argument,		NULL, optNoLegacy },
197	{ "no-macho",	no_argument,			NULL, optNoMachO },
198	{ "numeric-errors", no_argument,		NULL, optNumeric },
199	{ "team-identifier", required_argument,		NULL, optTeamID },
200	{ "prefix",		required_argument,		NULL, optIdentifierPrefix },
201	{ "preserve-metadata", optional_argument, NULL, optPreserveMetadata },
202	{ "procaction",	required_argument,		NULL, optProcAction },
203	{ "procinfo",	no_argument,			NULL, optProcInfo },
204	{ "remove-signature", no_argument,		NULL, optRemoveSignature },
205	{ "resource-rules", required_argument,	NULL, optResourceRules },
206	{ "revoked", no_argument,				NULL, optCheckRevocation },
207	{ "sdkroot", required_argument,			NULL, optSDKRoot },
208	{ "signature-size", required_argument,	NULL, optSignatureSize },
209	{ "signing-time", required_argument,	NULL, optSigningTime },
210	{ "timestamp",	optional_argument,		NULL, optTimestamp },
211	{ "no-tsa-certs",	no_argument,		NULL, optTSANoCerts },
212	{ "no-strict",	no_argument,			NULL, optNoStrict },
213	{ "seal-root",	no_argument,			NULL, optSealRoot },
214	{ }
215};
216
217
218//
219// codesign [options] bundle-path
220//
221int main(int argc, char *argv[])
222{
223	try {
224		const char *signerName = NULL;
225		int arg, argslot;
226		while (argslot = -1,
227				(arg = getopt_long(argc, argv, "a:dD:fhi:o:P:r:R:s:v", options, &argslot)) != -1)
228			switch (arg) {
229			case 'a':
230				chooseArchitecture(optarg);
231				staticVerifyOptions &= ~kSecCSCheckAllArchitectures;
232				break;
233			case 'd':
234				operation = doDump;
235				break;
236			case 'D':
237				detached = optarg;
238				break;
239			case 'f':
240				force = true;
241				break;
242			case 'h':
243				operation = doHostingInfo;
244				break;
245			case 'i':
246				uniqueIdentifier = optarg;
247				break;
248			case 'o':
249				cdFlags = parseCdFlags(optarg);
250				break;
251			case 'P':
252				{
253					if ((pagesize = atol(optarg))) {
254						int pslog;
255						if (frexp(pagesize, &pslog) != 0.5)
256							fail("page size must be a power of two");
257					}
258					break;
259				}
260			case 'r':
261				internalReq = optarg;
262				break;
263			case 'R':
264				testReq = optarg;
265				break;
266			case 's':
267				signerName = optarg;
268				operation = doSign;
269				break;
270			case 'v':
271				if (argslot < 0)								// -v
272					verbose++;
273				else if (options[argslot].has_arg == no_argument)
274					operation = doVerify;						// --verify
275				else if (optarg)
276					verbose = atoi(optarg);						// --verbose=level
277				else
278					verbose++;									// --verbose
279				break;
280
281			case optAllArchitectures:
282				allArchitectures = true;
283				staticVerifyOptions |= kSecCSCheckAllArchitectures;
284				break;
285			case optBundleVersion:
286				bundleVersion = optarg;
287				break;
288			case optCheckExpiration:
289				staticVerifyOptions |= kSecCSConsiderExpiration;
290				dynamicVerifyOptions |= kSecCSConsiderExpiration;
291				break;
292			case optCheckRevocation:
293				staticVerifyOptions |= kSecCSEnforceRevocationChecks;
294				dynamicVerifyOptions |= kSecCSEnforceRevocationChecks;
295				break;
296			case optContinue:
297				continueOnError = true;
298				break;
299			case optDeep:
300				nested = true;
301				signOptions |= kSecCSSignNestedCode;
302				staticVerifyOptions |= kSecCSCheckNestedCode;
303				break;
304			case optDetachedDatabase:
305				if (optarg)
306					detachedDb = optarg;
307				else
308					detachedDb = "system";
309				break;
310			case optDigestAlgorithm:
311				digestAlgorithm = findHashType(optarg)->code;
312				break;
313			case optDryRun:
314				dryrun = true;
315				break;
316			case optCodeDirectory:
317				dumpBinary = optarg;
318				break;
319			case optEntitlements:
320				entitlements = optarg;
321				break;
322			case optExtractCerts:
323				if (optarg)
324					extractCerts = optarg;
325				else
326					extractCerts = "./codesign";
327				break;
328			case optFeatures:
329				if (optarg)
330					featureCheck = optarg;
331				else {
332					for (const char **p = features; *p; p++)
333						printf("%s\n", *p);
334					exit(0);
335				}
336				break;
337			case optFileList:
338				modifiedFiles = optarg;
339				break;
340			case optTeamID:
341				if (checkTeamId(optarg))
342					teamID = optarg;
343				else
344					fail("TeamIdentifier must be at least 1 and no more than %d alphanumeric characters", TEAM_ID_MAX);
345				break;
346			case optIdentifierPrefix:
347				identifierPrefix = optarg;
348				break;
349			case optIgnoreResources:
350				staticVerifyOptions |= kSecCSDoNotValidateResources;
351				break;
352			case optKeychain:
353				MacOSError::check(keychain_open(optarg, keychain));
354				break;
355			case optLegacy:
356				signOptions |= kSecCSSignV1;
357				break;
358			case optFMJ:
359				signOptions |= kSecCSSignOpaque;	// no need for V2 signature for FMJ
360				break;
361			case optNoLegacy:
362				signOptions |= kSecCSSignNoV1;
363				break;
364			case optNoMachO:
365				noMachO = true;
366				break;
367			case optNumeric:
368				numericErrors = true;
369				break;
370			case optTimestamp:
371				if (optarg && !strcmp(optarg, "none")) {	// explicit defeat
372					timestampRequest = kCFBooleanFalse;
373				} else {
374					timestampRequest = kCFBooleanTrue;
375					tsaURL = optarg;
376				}
377				break;
378            case optTSANoCerts:
379 				noTSAcerts = true;
380				break;
381			case optPreserveMetadata:
382				preserveMetadata = parseMetadataFlags(optarg);
383				break;
384			case optProcAction:
385				operation = doProcAction;
386				procAction = optarg;
387				break;
388			case optProcInfo:
389				operation = doProcInfo;
390				break;
391			case optRemoveSignature:
392				signerName = NULL;
393				operation = doSign;		// well, un-sign
394				break;
395			case optResourceRules:
396				resourceRules = optarg;
397				note(0, "Warning: --resource-rules has been deprecated in Mac OS X >= 10.10!");
398				break;
399			case optSDKRoot:
400				sdkRoot = optarg;
401				break;
402			case optSignatureSize:
403				signatureSize = atol(optarg);
404				break;
405			case optSigningTime:
406				signingTime = parseDate(optarg);
407				break;
408			case optNoStrict:
409				signOptions &= ~kSecCSSignStrictPreflight;
410				staticVerifyOptions &= ~kSecCSStrictValidate;
411				break;
412			case optSealRoot:
413				signOptions |= kSecCSSignBundleRoot;
414				break;
415
416			case '?':
417				usage();
418			}
419
420		if (signerName)
421			signer = findIdentity(keychain, signerName);
422	} catch (...) {
423		diagnose(NULL, exitFailure);
424	}
425
426	// -v does double duty as -v(erbose) and -v(erify)
427	if (operation == doNothing && verbose) {
428		operation = doVerify;
429		verbose--;
430	}
431	if (featureCheck) {
432		checkFeatures(featureCheck);
433		if (operation == doNothing)
434			exit(0);
435	}
436	if (operation == doNothing || optind == argc)
437		usage();
438
439	// masticate the more interesting arguments
440	try {
441		switch (operation) {
442		case doSign:
443			prepareToSign();
444			break;
445		case doVerify:
446			prepareToVerify();
447			break;
448		default:
449			break;
450		}
451	} catch (...) {
452		diagnose(NULL, exitFailure);
453	}
454
455	// operate on paths given after options
456	for ( ; optind < argc; optind++) {
457		const char *target = argv[optind];
458		try {
459			switch (operation) {
460			case doSign:
461				sign(target);
462				break;
463			case doVerify:
464				verify(target);
465				break;
466			case doDump:
467				dump(target);
468				break;
469			case doHostingInfo:
470				hostinginfo(target);
471				break;
472			case doProcInfo:
473				procinfo(target);
474				break;
475			case doProcAction:
476				procaction(target);
477				break;
478			default:
479				break;
480			}
481		} catch (...) {
482			diagnose(target);
483			if (!exitcode)
484				exitcode = exitFailure;
485			if (!continueOnError)
486				exit(exitFailure);
487		}
488	}
489
490	exit(exitcode);
491}
492
493void usage()
494{
495	fprintf(stderr, "Usage: codesign -s identity [-fv*] [-o flags] [-r reqs] [-i ident] path ... # sign\n"
496		"       codesign -v [-v*] [-R testreq] path|[+]pid ... # verify\n"
497		"       codesign -d [options] path ... # display contents\n"
498		"       codesign -h pid ... # display hosting paths\n"
499	);
500	exit(exitUsage);
501}
502
503OSStatus
504keychain_open(const char *name, SecKeychainRef &keychain)
505{
506	OSStatus result;
507
508	if (name && name[0] != '/')
509	{
510		CFArrayRef dynamic = NULL;
511		result = SecKeychainCopyDomainSearchList(
512			kSecPreferencesDomainDynamic, &dynamic);
513		if (result)
514			return result;
515		else
516		{
517			uint32_t i;
518			uint32_t count = dynamic ? CFArrayGetCount(dynamic) : 0;
519
520			for (i = 0; i < count; ++i)
521			{
522				char pathName[PATH_MAX];
523				UInt32 ioPathLength = sizeof(pathName);
524				bzero(pathName, ioPathLength);
525				keychain = (SecKeychainRef)CFArrayGetValueAtIndex(dynamic, i);
526				result = SecKeychainGetPath(keychain, &ioPathLength, pathName);
527				if (result)
528					return result;
529
530				if (!strncmp(pathName, name, ioPathLength))
531				{
532					CFRetain(keychain);
533					CFRelease(dynamic);
534					return noErr;
535				}
536			}
537			CFRelease(dynamic);
538		}
539	}
540
541	return SecKeychainOpen(name, &keychain);
542}
543
544static bool checkTeamId(const char *teamID)
545{
546	if (!teamID)
547		return false;
548
549	size_t id_len = strnlen(teamID, TEAM_ID_MAX+1);
550	if (id_len > TEAM_ID_MAX || id_len < 1)
551		return false;
552
553	for (size_t i = 0; i < id_len; i++) {
554		if (!isalnum(teamID[i]))
555			return false;
556	}
557	return true;
558}
559
560void chooseArchitecture(const char *arg)
561{
562	int arch, subarch;
563	switch (sscanf(arg, "%d,%d", &arch, &subarch)) {
564	case 0:		// not a number
565		if (!(architecture = Architecture(arg)))
566			fail("%s: unknown architecture name", arg);
567		break;
568	case 1:
569		architecture = Architecture(arch);
570		break;
571	case 2:
572		architecture = Architecture(arch, subarch);
573		break;
574	}
575}
576
577
578static uint32_t parseMetadataFlags(const char *arg)
579{
580	static const SecCodeDirectoryFlagTable metadataFlags[] = {
581		{ "identifier",	kSecCodeSignerPreserveIdentifier,		true },
582		{ "requirements", kSecCodeSignerPreserveRequirements,	true },
583		{ "entitlements", kSecCodeSignerPreserveEntitlements,	true },
584		{ "resource-rules", kSecCodeSignerPreserveResourceRules,true },
585		{ "flags", kSecCodeSignerPreserveFlags,					true },
586		{ "team-identifier", kSecCodeSignerPreserveTeamIdentifier, true},
587		{ NULL }
588	};
589	uint32_t flags;
590	if (arg == NULL) {	// --preserve-metadata compatibility default
591		flags = kSecCodeSignerPreserveRequirements | kSecCodeSignerPreserveEntitlements | kSecCodeSignerPreserveResourceRules | kSecCodeSignerPreserveFlags;
592		if (!getenv("RC_XBS") || getenv("RC_BUILDIT"))	// if we're NOT in real B&I...
593			flags |= kSecCodeSignerPreserveIdentifier;				// ... then preserve identifier too
594		note(0, "Warning: default usage of --preserve-metadata implies \"resource-rules\" (deprecated in Mac OS X >= 10.10)!");
595	} else {
596		flags = parseOptionTable(arg, metadataFlags);
597		if (flags & kSecCodeSignerPreserveResourceRules) {
598			note(0, "Warning: usage of --preserve-metadata with option \"resource-rules\" (deprecated in Mac OS X >= 10.10)!",
599				 options[optPreserveMetadata].name, options[optResourceRules]);
600		}
601	}
602
603	return flags;
604}
605
606
607//
608// Exit unless each of the comma-separated feature names is supported
609// by this version of codesign(1).
610//
611void checkFeatures(const char *arg)
612{
613	while (true) {
614		const char *comma = strchr(arg, ',');
615		string feature = comma ? string(arg, comma-arg) : arg;
616		if (feature.empty())
617			fail("Invalid feature name");
618		const char **p;
619		for (p = features; *p && feature != *p; p++) ;
620		if (!*p)
621			fail("%s: not supported in this version", feature.c_str());
622		if (comma) {
623			arg = comma + 1;
624			if (!*arg)	// tolerate trailing comma
625				break;
626		} else {
627			break;
628		}
629	}
630}
631