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