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_utils - shared utilities for CodeSigning tool commands
26//
27#include "codesign.h"
28#include <Security/CodeSigning.h>
29#include <Security/SecCertificatePriv.h>
30#include <Security/CSCommonPriv.h>
31#include <Security/SecIdentitySearchPriv.h>
32#include <Security/SecPolicyPriv.h>
33#include <security_utilities/cfutilities.h>
34#include <security_utilities/cfmunge.h>
35#include <security_codesigning/reqdumper.h>
36#include <security_codesigning/cdbuilder.h>
37#include <security_codesigning/reqparser.h>
38#include <Security/CMSEncoder.h>
39#include <cstdio>
40#include <cmath>
41#include <getopt.h>
42#include <sys/codesign.h>
43#include <sys/param.h>		// MAXPATHLEN
44
45using namespace UnixPlusPlus;
46
47
48//
49// Shared command-line arguments and options
50//
51unsigned verbose = 0;				// verbosity level
52bool force = false;					// force overwrite flag
53bool continueOnError = false;		// continue processing targets on error(s)
54bool numericErrors = false;			// display errors as numbers (for mechanized callers)
55
56int exitcode = exitSuccess;			// cumulative exit code
57
58
59//
60// Convert between hash code numbers and human-readable form.
61// We accept unambiguous prefix strings for conversion.
62//
63static const HashType hashTypes[] = {
64	{ "sha1",			kSecCodeSignatureHashSHA1,						SHA1::digestLength },
65	{ "sha256",			kSecCodeSignatureHashSHA256,					256 / 8 },
66	{ NULL }
67};
68
69const HashType *findHashType(const char *hashName)
70{
71	size_t length = strlen(hashName);
72	const HashType *match = NULL;
73	for (const HashType *h = hashTypes; h->name; h++) {
74		if (!strncmp(hashName, h->name, length)) {	// prefix match
75			if (match) {
76				fail("%s: ambiguous hash specification (%s or %s)",
77					hashName, match->name, h->name);
78			} else
79				match = h;
80        }
81    }
82	if (match)
83		return match;
84	fail("%s: unknown hash specification", hashName);
85}
86
87const HashType *findHashType(uint32_t hashCode)
88{
89	for (const HashType *h = hashTypes; h->name; h++)
90		if (h->code == hashCode)
91			return h;
92	return NULL;
93}
94
95
96//
97// Build requirements data from outside sources.
98// This automatically recognizes binary Requirement(s) blobs, on the
99// assumption that the high byte of their magic is not a valid
100// (first) character of a text Requirements syntax. The assumption is
101// that they all share the same first-byte prefix (0xfa, for the 0xfade0cxx
102// convention used for code signing magic numbers).
103//
104CFTypeRef readRequirement(const string &source, SecCSFlags flags)
105{
106	CFTypeRef result;
107	ErrorCheck check;
108	if (source[0] == '=') {	// =text
109		if (flags)
110			check(SecRequirementsCreateWithString(CFTempString(source.substr(1)), flags, &result, check));
111		else
112			result = makeCFString(source.substr(1));
113		return result;
114	}
115	FILE *f;
116	if (source == "-") {
117		f = stdin;
118	} else if (!(f = fopen(source.c_str(), "r"))) {
119		perror(source.c_str());
120		fail("invalid requirement specification");
121	}
122	int first = getc(f);
123	ungetc(first, f);
124	if (first == kSecCodeMagicByte) {			// binary blob
125		BlobCore *blob = BlobCore::readBlob(f);
126		switch (blob->magic()) {
127		case kSecCodeMagicRequirement:
128			MacOSError::check(SecRequirementCreateWithData(CFTempData(*blob), kSecCSDefaultFlags, (SecRequirementRef *)&result));
129			break;
130		case kSecCodeMagicRequirementSet:
131			result = makeCFData(*blob);
132			break;
133		default:
134			fail((source + ": not a recognized requirement file").c_str());
135		}
136		::free(blob);
137	} else {									// presumably text
138		char buffer[10240];		// arbitrary size
139		size_t length = fread(buffer, 1, sizeof(buffer) - 1, f);
140		buffer[length] = '\0';
141		if (flags)
142			check(SecRequirementsCreateWithString(CFTempString(buffer), flags, &result, check));
143		else
144			result = makeCFString(buffer);
145	}
146	if (f != stdin)
147		fclose(f);
148	return result;
149}
150
151
152//
153// ErrorCheck
154//
155void ErrorCheck::throwError()
156{
157	assert(mError);
158	throw Error(mError);
159}
160
161
162//
163// Given a keychain or type of keychain item, return the string form of the path
164// of the keychain containing it.
165//
166string keychainPath(CFTypeRef whatever)
167{
168	CFRef<SecKeychainRef> keychain;
169	if (CFGetTypeID(whatever) == SecKeychainGetTypeID()) {
170		keychain = SecKeychainRef(whatever);
171	} else if (CFGetTypeID(whatever) == SecIdentityGetTypeID()) {
172		CFRef<SecCertificateRef> cert;
173		MacOSError::check(SecIdentityCopyCertificate(SecIdentityRef(whatever), &cert.aref()));
174		MacOSError::check(SecKeychainItemCopyKeychain(cert.as<SecKeychainItemRef>(), &keychain.aref()));
175	} else {	// assume keychain item
176		switch (OSStatus rc = SecKeychainItemCopyKeychain(SecKeychainItemRef(whatever), &keychain.aref())) {
177		case noErr:
178			break;
179		case errSecNoSuchKeychain:
180			return "(nowhere)";
181		default:
182			MacOSError::throwMe(rc);
183		}
184	}
185	char path[MAXPATHLEN];
186	UInt32 length = sizeof(path);
187	MacOSError::check(SecKeychainGetPath(keychain, &length, path));
188	return path;
189}
190
191
192//
193// Locate a signing identity from the keychain search list.
194// Exit with error diagnosed if we can't find exactly one match in the user's
195// keychain environment.
196//
197static SecIdentityRef findIdentity(SecKeychainRef keychain, const char *match, SecPolicyRef policy);
198
199SecIdentityRef findIdentity(SecKeychainRef keychain, const char *match)
200{
201	// the special value "-" (dash) indicates ad-hoc signing
202	if (!strcmp(match, "-"))
203		return SecIdentityRef(kCFNull);
204
205	// interpret the match as a (full) identity preference name
206	CFRef<SecIdentityRef> preference;
207	switch (OSStatus rc = SecIdentityCopyPreference(CFTempString(match), 0, NULL, &preference.aref())) {
208	case noErr:
209		if (preference)
210			return preference.yield();
211		break;
212	case errSecItemNotFound:
213		break;
214	default:
215		MacOSError::throwMe(rc);
216	}
217
218	// look for qualified identities
219	CFRef<SecPolicyRef> policy;
220	MacOSError::check(SecPolicyCopy(CSSM_CERT_X_509v3,
221		&CSSMOID_APPLE_TP_CODE_SIGNING, &policy.aref()));
222	if (SecIdentityRef identity = findIdentity(keychain, match, policy))
223		return identity;	// good match
224
225	// now try again, using the generic policy
226	MacOSError::check(SecPolicyCopy(CSSM_CERT_X_509v3,
227		&CSSMOID_APPLE_X509_BASIC, &policy.aref()));
228	if (SecIdentityRef identity = findIdentity(keychain, match, policy)) {
229		// found a match, but it's not good for Code Signing
230#if !defined(NDEBUG)
231		if (getenv("CODESIGN_ANYCERT")) {
232			note(1, "Using unqualified identity for test - signature will not verify");
233			return identity;
234		}
235#endif //NDEBUG
236		CFRef<SecCertificateRef> cert;
237		MacOSError::check(SecIdentityCopyCertificate(identity, &cert.aref()));
238		CFRef<CFStringRef> name;
239		MacOSError::check(SecCertificateCopyCommonName(cert, &name.aref()));
240		fail("%s: this identity cannot be used for signing code", cfString(name).c_str());
241	}
242	fail("%s: no identity found", match);
243}
244
245
246static SecIdentityRef findIdentity(SecKeychainRef keychain, const char *match, SecPolicyRef policy)
247{
248	CFRef<SecIdentitySearchRef> search;
249	MacOSError::check(SecIdentitySearchCreateWithPolicy(policy, NULL,
250			CSSM_KEYUSE_SIGN, keychain, false, &search.aref()));
251
252	// recognize an exact hex expression of a SHA1 (no abbreviations allowed)
253	CFRef<CFDataRef> certHash;
254	const char hexDigits[] = "0123456789abcdefABCDEF";
255	if (strlen(match) == 2 * SHA1::digestLength && strspn(match, hexDigits) == 2 * SHA1::digestLength) {
256		SHA1::Digest digest;
257		stringHash(match, digest);
258		certHash = CFDataCreate(NULL, digest, sizeof(digest));
259	}
260
261	// filter all candidates against our match constraint
262	CFRef<CFStringRef> cfmatch = makeCFString(match);
263	CFRef<SecIdentityRef> bestMatch;
264	CFRef<CFStringRef> bestMatchName;
265	bool exactMatch = false;
266	CSSM_DATA bestMatchData;
267	for (;;) {
268		CFRef<SecIdentityRef> candidate;
269		switch (OSStatus rc = SecIdentitySearchCopyNext(search, &candidate.aref())) {
270		case noErr:
271			{
272				CFRef<SecCertificateRef> cert;
273				MacOSError::check(SecIdentityCopyCertificate(candidate, &cert.aref()));
274
275				// match on certificate hash if that was requested
276				if (certHash) {
277					CFRef<CFDataRef> hash = certificateHash(cert);
278					if (CFEqual(hash, certHash))
279						return candidate.yield();
280				}
281
282				// otherwise, match on best CN substring
283				CFRef<CFStringRef> name;
284				CSSM_DATA data;
285				MacOSError::check(SecCertificateCopyCommonName(cert, &name.aref()));
286				MacOSError::check(SecCertificateGetData(cert, &data));
287				if (!strcmp(match, "*")) {	// means "just take the first one"
288					note(1, "Using identity \"%s\"", cfString(name).c_str());
289					return candidate.yield();
290				}
291				if (!name)		// certificate has no subject common name (can't find it by name)
292					continue;
293				CFRange find = CFStringFind(name, cfmatch,
294					kCFCompareCaseInsensitive | kCFCompareNonliteral);
295				if (find.location == kCFNotFound)
296					continue;		// no match
297				bool isExact = find.location == 0 && find.length == CFStringGetLength(name);
298				if (bestMatch) {	// got two matches
299					if (exactMatch && !isExact)		// prior is better; ignore this one
300						continue;
301					if (exactMatch == isExact) {	// same class of match
302						if (bestMatchData.Length == data.Length
303								&& !memcmp(bestMatchData.Data, data.Data, data.Length)) { // same cert
304							note(2, "%s: found in both %s and %s (this is all right)",
305								match, keychainPath(bestMatch).c_str(), keychainPath(cert).c_str());
306							continue;
307						}
308						// ambiguity - fail and try to explain it as well as we can
309						string firstKeychain = keychainPath(bestMatch);
310						string newKeychain = keychainPath(cert);
311						if (firstKeychain == newKeychain)
312							fail("%s: ambiguous (matches \"%s\" and \"%s\" in %s)",
313								match, cfString(name).c_str(), cfString(bestMatchName).c_str(),
314								newKeychain.c_str());
315						else
316							fail("%s: ambiguous (matches \"%s\" in %s and \"%s\" in %s)",
317								match, cfString(name).c_str(), firstKeychain.c_str(),
318								cfString(bestMatchName).c_str(), newKeychain.c_str());
319					}
320				}
321				bestMatch = candidate;
322				bestMatchName = name;
323				bestMatchData = data;
324				exactMatch = isExact;
325				break;
326			}
327		case errSecItemNotFound:
328			return bestMatch.yield();
329		default:
330			MacOSError::check(rc);
331		}
332	}
333}
334
335
336//
337// Parse all forms of the -o/--options argument (CodeDirectory flags)
338//
339uint32_t parseOptionTable(const char *arg, const SecCodeDirectoryFlagTable *options)
340{
341	uint32_t flags = 0;
342	std::string text = arg;
343	for (string::size_type comma = text.find(','); ; text = text.substr(comma+1), comma = text.find(',')) {
344		string word = (comma == string::npos) ? text : text.substr(0, comma);
345		const SecCodeDirectoryFlagTable *item;
346		for (item = options; item->name; item++)
347			if (item->signable && !strncmp(word.c_str(), item->name, word.size())) {
348				flags |= item->value;
349				break;
350			}
351		if (!item->name)  // not found
352			MacOSError::throwMe(errSecCSInvalidFlags);
353		if (comma == string::npos)  // last word
354			break;
355	}
356    return flags;
357}
358
359uint32_t parseCdFlags(const char *arg)
360{
361	// if it's numeric, Just Do It
362	if (isdigit(arg[0])) {		// numeric - any form of number
363		char *remain;
364		uint32_t flags = uint32_t(strtol(arg, &remain, 0));
365		if (remain[0])
366			fail("%s: invalid flag(s)", arg);
367		return flags;
368	} else {
369		return parseOptionTable(arg, kSecCodeDirectoryFlagTable);
370    }
371}
372
373
374//
375// Parse a date/time string into CFDateRef form.
376// The special value "none" is translated to cfNull.
377//
378CF_EXPORT CFStringRef const kCFDateFormatterIsLenientKey;
379
380CFDateRef parseDate(const char *string)
381{
382	if (!string || !strcasecmp(string, "none"))
383		return CFDateRef(kCFNull);
384	CFRef<CFLocaleRef> userLocale = CFLocaleCopyCurrent();
385	CFRef<CFDateFormatterRef> formatter = CFDateFormatterCreate(NULL, userLocale,
386		kCFDateFormatterMediumStyle, kCFDateFormatterMediumStyle);
387	CFDateFormatterSetProperty(formatter, kCFDateFormatterIsLenientKey, kCFBooleanTrue);
388	CFRef<CFDateRef> date = CFDateFormatterCreateDateFromString(NULL, formatter, CFTempString(string), NULL);
389	if (!date)
390		fail("%s: invalid date/time", string);
391	return date.yield();
392}
393
394
395//
396// Clean up a pathname.
397//
398std::string cleanPath(const char *path)
399{
400	char answer[PATH_MAX];
401	if (const char *r = realpath(path, answer))
402		return r;
403	UnixError::throwMe();
404}
405
406
407//
408// Convert a SHA-1 hash into a string of hex digits (40 of them, of course)
409//
410std::string hashString(const SHA1::Byte *hash)
411{
412	char s[2 * SHA1::digestLength + 1];
413	for (unsigned n = 0; n < SHA1::digestLength; n++)
414		sprintf(&s[2*n], "%2.2x", hash[n]);
415	return s;
416}
417
418std::string hashString(SHA1 &hash)
419{
420	SHA1::Digest digest;
421	hash.finish(digest);
422	return hashString(digest);
423}
424
425void stringHash(const char *string, SHA1::Digest digest)
426{
427	for (unsigned n = 0; n < SHA1::digestLength; n++)
428		sscanf(string+2*n, "%2hhx", digest+n);
429}
430
431CFDataRef certificateHash(SecCertificateRef cert)
432{
433	CFRef<CFDataRef> certData = SecCertificateCopyData(cert);
434	SHA1 hash;
435	hash(CFDataGetBytePtr(certData), CFDataGetLength(certData));
436	SHA1::Digest digest;
437	hash.finish(digest);
438	return CFDataCreate(NULL, digest, sizeof(digest));
439}
440
441
442//
443// Diagnostic and messaging support
444//
445void fail(const char *format, ...)
446{
447	va_list args;
448	va_start(args, format);
449	vfprintf(stderr, format, args);
450	fprintf(stderr, "\n");
451	va_end(args);
452	if (continueOnError)
453		throw Fail(exitFailure);
454	else
455		exit(exitFailure);
456}
457
458void note(unsigned level, const char *format, ...)
459{
460	if (verbose >= level) {
461		va_list args;
462		va_start(args, format);
463		vfprintf(stderr, format, args);
464		fprintf(stderr, "\n");
465		va_end(args);
466	}
467}
468
469
470//
471// Resolve an exception into a readable error message, then exit with error
472//
473static void diagnose1(const char *type, CFTypeRef value);
474static void diagnose1(const char *context, OSStatus rc);
475
476void diagnose(const char *context /* = NULL */, int stop /* = 0 */)
477{
478	try {
479		throw;
480	} catch (const ErrorCheck::Error &err) {
481		diagnose(context, err.error);
482	} catch (const MacOSError &err) {
483		diagnose1(context, err.osStatus());
484	} catch (const UnixError &err) {
485		errno = err.error;
486		perror(context);
487	} catch (const Fail &failure) {
488		// source has printed any error information
489	} catch (...) {
490		if (context)
491			fprintf(stderr, "%s: ", context);
492		fprintf(stderr, "unknown exception\n");
493	}
494	if (stop)
495		exit(stop);
496}
497
498void diagnose(const char *context, CFErrorRef err)
499{
500	diagnose(context, OSStatus(CFErrorGetCode(err)), CFErrorCopyUserInfo(err));
501}
502
503static void diagnose1(const char *context, OSStatus rc)
504{
505	if (numericErrors)
506		printf("%ld\n", long(rc));
507	if (rc >= errSecErrnoBase && rc < errSecErrnoLimit) {
508		errno = rc - errSecErrnoBase;
509		perror(context);
510	} else
511		cssmPerror(context, rc);
512}
513
514void diagnose(const char *context, OSStatus rc, CFDictionaryRef info)
515{
516	diagnose1(context, rc);
517
518	if (CFTypeRef path = CFDictionaryGetValue(info, kSecCFErrorPath))
519			fprintf(stderr, "In subcomponent: %s\n", cfString(CFURLRef(path)).c_str());
520
521	if (CFTypeRef detail = CFDictionaryGetValue(info, kSecCFErrorArchitecture))
522			fprintf(stderr, "In architecture: %s\n", cfString(CFStringRef(detail)).c_str());
523
524	if (CFTypeRef detail = CFDictionaryGetValue(info, kSecCFErrorRequirementSyntax))
525			fprintf(stderr, "Requirement syntax error(s):\n%s", cfString(CFStringRef(detail)).c_str());
526
527	if (verbose) {
528		if (CFTypeRef detail = CFDictionaryGetValue(info, kSecCFErrorResourceAdded))
529			diagnose1("file added", detail);
530		if (CFTypeRef detail = CFDictionaryGetValue(info, kSecCFErrorResourceAltered))
531			diagnose1("file modified", detail);
532		if (CFTypeRef detail = CFDictionaryGetValue(info, kSecCFErrorResourceMissing))
533			diagnose1("file missing", detail);
534	}
535}
536
537static void diagnose1(const char *type, CFTypeRef value)
538{
539	if (CFGetTypeID(value) == CFArrayGetTypeID()) {
540		CFArrayRef array = CFArrayRef(value);
541		CFIndex size = CFArrayGetCount(array);
542		for (CFIndex n = 0; n < size; n++)
543			diagnose1(type, CFArrayGetValueAtIndex(array, n));
544	} else
545		printf("%s: %s\n", type, cfString(value, noErr).c_str());
546}
547
548
549//
550// Take an array of CFURLRefs and write it to a file as a list of filesystem paths,
551// one path per line.
552//
553void writeFileList(CFArrayRef list, const char *destination, const char *mode)
554{
555	FILE *out;
556	if (!strcmp(destination, "-")) {
557		out = stdout;
558	} else if (!(out = fopen(destination, mode))) {
559		perror(destination);
560		exit(1);
561	}
562	CFIndex count = CFArrayGetCount(list);
563	for (CFIndex n = 0; n < count; n++)
564		fprintf(out, "%s\n", cfString(CFURLRef(CFArrayGetValueAtIndex(list, n))).c_str());
565	if (strcmp(destination, "-"))
566		fclose(out);
567}
568
569
570//
571// Take a CFDictionary and write it to a file as a property list.
572//
573void writeDictionary(CFDictionaryRef dict, const char *destination, const char *mode)
574{
575	FILE *out;
576	if (!strcmp(destination, "-")) {
577		out = stdout;
578	} else if (!(out = fopen(destination, mode))) {
579		perror(destination);
580		exit(1);
581	}
582	if (dict) {
583		CFRef<CFDataRef> data = makeCFData(dict);
584		fwrite(CFDataGetBytePtr(data), 1, CFDataGetLength(data), out);
585	}
586	if (strcmp(destination, "-"))
587		fclose(out);
588}
589
590
591//
592// Take a CFDictionary and write it to a file as a property list.
593//
594void writeData(CFDataRef data, const char *destination, const char *mode)
595{
596	FILE *out;
597	if (!strcmp(destination, "-")) {
598		out = stdout;
599	} else if (!(out = fopen(destination, mode))) {
600		perror(destination);
601		exit(1);
602	}
603	if (data)
604		fwrite(CFDataGetBytePtr(data), 1, CFDataGetLength(data), out);
605	if (strcmp(destination, "-"))
606		fclose(out);
607}
608
609
610//
611// Create a static code object, using command context as available
612//
613SecStaticCodeRef staticCodePath(const char *target, const Architecture &arch, const char *version)
614{
615	CFRef<CFMutableDictionaryRef> attributes;
616	if (arch || version) {
617		attributes = makeCFMutableDictionary();
618		if (arch)
619			cfadd(attributes, "{%O=%d,%O=%d}",
620				kSecCodeAttributeArchitecture, arch.cpuType(),
621				kSecCodeAttributeSubarchitecture, arch.cpuSubtype());;
622		if (version)
623			cfadd(attributes, "{%O=%s}", kSecCodeAttributeBundleVersion, version);
624	}
625	SecStaticCodeRef code;
626	MacOSError::check(SecStaticCodeCreateWithPathAndAttributes(CFTempURL(cleanPath(target)), kSecCSDefaultFlags,
627		attributes, &code));
628	return code;
629}
630
631
632//
633// Accept various forms of dynamic target specifications
634// and return a SecCodeRef for the designated code.
635// Returns NULL if the syntax isn't recognized as a dynamic
636// host designation. Fails (calls fail which exits) if the
637// argument looks dynamic but has bad syntax.
638//
639static SecCodeRef descend(SecCodeRef host, CFRef<CFMutableDictionaryRef> attrs, string path);
640static void parsePath(CFMutableDictionaryRef attrs, string form);
641static void parseAttribute(CFMutableDictionaryRef attrs, string form);
642
643SecCodeRef dynamicCodePath(const char *target)
644{
645    CFRef<CFMutableDictionaryRef> dict = NULL;
646
647    if (target[0] == '+') {
648		dict = cfmake<CFMutableDictionaryRef>("{%O=#T}", kSecGuestAttributeDynamicCode);
649		target++;
650    } else
651		dict = makeCFMutableDictionary();
652
653	if (!isdigit(target[0]))
654		return NULL;	// not a dynamic spec
655
656	char *path;
657	int pid = (int)strtol(target, &path, 10);
658    if (pid == 0)
659        return NULL;
660
661	return descend(NULL,
662                   cfmake<CFMutableDictionaryRef>("{+%O,%O=%d}", dict.get(), kSecGuestAttributePid, pid),
663                   path);
664}
665
666
667static SecCodeRef descend(SecCodeRef host, CFRef<CFMutableDictionaryRef> attrs, string path)
668{
669	string::size_type colon = path.find(':');
670	if (colon == string::npos)	// last element
671		parsePath(attrs, path);
672	else
673		parsePath(attrs, path.substr(0, colon));
674	CFRef<SecCodeRef> guest;
675	MacOSError::check(SecCodeCopyGuestWithAttributes(host, attrs, kSecCSDefaultFlags, &guest.aref()));
676	if (colon == string::npos)
677		return guest.yield();
678	else
679		return descend(guest, makeCFMutableDictionary(), path.substr(colon + 1));
680}
681
682
683// generate a guest selector dictionary for an attr=value specification
684static void parsePath(CFMutableDictionaryRef attrs, string form)
685{
686	for (string::size_type comma = form.find(','); comma != string::npos; comma = form.find(',')) {
687		parseAttribute(attrs, form.substr(0, comma));
688		form = form.substr(comma + 1);
689	}
690	parseAttribute(attrs, form);
691}
692
693static void parseAttribute(CFMutableDictionaryRef attrs, string form)
694{
695	if (form.empty())		// nothing to add
696		return;
697	string::size_type eq = form.find('=');
698	CFRef<CFStringRef> key;
699	if (eq == string::npos) {
700		key = kSecGuestAttributeCanonical;
701	} else {
702		key.take(makeCFString(form.substr(0, eq)));
703		form = form.substr(eq + 1);
704	}
705
706	if (isdigit(form[0]))	// number
707		CFDictionaryAddValue(attrs, key, CFTempNumber(strtol(form.c_str(), NULL, 0)));
708	else					// string
709		CFDictionaryAddValue(attrs, key, CFTempString(form));
710}
711