1/*
2 * Copyright (c) 2003-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 * identity_find.c
24 */
25
26#include "identity_find.h"
27#include "keychain_utilities.h"
28#include "trusted_cert_utils.h"
29#include "security.h"
30
31#include <stdio.h>
32#include <stdlib.h>
33#include <string.h>
34#include <unistd.h>
35#include <Security/cssmtype.h>
36#include <Security/oidsalg.h>
37#include <Security/SecCertificate.h>
38#include <Security/SecIdentity.h>
39#include <Security/SecIdentitySearch.h>
40#include <Security/SecPolicySearch.h>
41#include <Security/SecTrust.h>
42
43// cssmErrorString
44#include <Security/SecBasePriv.h>
45// SecCertificateInferLabel, SecDigestGetData
46#include <Security/SecCertificatePriv.h>
47// SecIdentitySearchCreateWithPolicy
48#include <Security/SecIdentitySearchPriv.h>
49
50
51SecIdentityRef
52find_identity(CFTypeRef keychainOrArray,
53			  const char *identity,
54			  const char *hash,
55			  CSSM_KEYUSE keyUsage)
56{
57	SecIdentityRef identityRef = NULL;
58	SecIdentitySearchRef searchRef = NULL;
59	OSStatus status = SecIdentitySearchCreate(keychainOrArray, keyUsage, &searchRef);
60	if (status) {
61		return identityRef;
62	}
63
64	// check input hash string and convert to data
65	CSSM_DATA hashData = { 0, NULL };
66	if (hash) {
67		CSSM_SIZE len = strlen(hash)/2;
68		hashData.Length = len;
69		hashData.Data = (uint8 *)malloc(hashData.Length);
70		fromHex(hash, &hashData);
71	}
72
73	// filter candidates against the hash (or the name, if no hash provided)
74	CFStringRef matchRef = (identity) ? CFStringCreateWithCString(NULL, identity, kCFStringEncodingUTF8) : NULL;
75	Boolean exactMatch = FALSE;
76
77	CSSM_DATA certData = { 0, NULL };
78	SecIdentityRef candidate = NULL;
79
80	while (SecIdentitySearchCopyNext(searchRef, &candidate) == noErr) {
81		SecCertificateRef cert = NULL;
82		if (SecIdentityCopyCertificate(candidate, &cert) != noErr) {
83			safe_CFRelease(&candidate);
84			continue;
85		}
86		if (SecCertificateGetData(cert, &certData) != noErr) {
87			safe_CFRelease(&cert);
88			safe_CFRelease(&candidate);
89			continue;
90		}
91		if (hash) {
92			uint8 candidate_sha1_hash[20];
93			CSSM_DATA digest;
94			digest.Length = sizeof(candidate_sha1_hash);
95			digest.Data = candidate_sha1_hash;
96			if ((SecDigestGetData(CSSM_ALGID_SHA1, &digest, &certData) == CSSM_OK) &&
97				(hashData.Length == digest.Length) &&
98				(!memcmp(hashData.Data, digest.Data, digest.Length))) {
99				exactMatch = TRUE;
100				identityRef = candidate; // currently retained
101				safe_CFRelease(&cert);
102				break; // we're done - can't get more exact than this
103			}
104		} else {
105			// copy certificate name
106			CFStringRef nameRef = NULL;
107			if ((SecCertificateCopyCommonName(cert, &nameRef) != noErr) || nameRef == NULL) {
108				safe_CFRelease(&cert);
109				safe_CFRelease(&candidate);
110				continue; // no name, so no match is possible
111			}
112			CFIndex nameLen = CFStringGetLength(nameRef);
113			CFIndex bufLen = 1 + CFStringGetMaximumSizeForEncoding(nameLen, kCFStringEncodingUTF8);
114			char *nameBuf = (char *)malloc(bufLen);
115			if (!CFStringGetCString(nameRef, nameBuf, bufLen-1, kCFStringEncodingUTF8))
116				nameBuf[0]=0;
117
118			if (!strcmp(identity, "*")) {	// special case: means "just take the first one"
119				sec_error("Using identity \"%s\"", nameBuf);
120				identityRef = candidate; // currently retained
121				free(nameBuf);
122				safe_CFRelease(&nameRef);
123				safe_CFRelease(&cert);
124				break;
125			}
126			CFRange find = { kCFNotFound, 0 };
127			if (nameRef && matchRef)
128				find = CFStringFind(nameRef, matchRef, kCFCompareCaseInsensitive | kCFCompareNonliteral);
129			Boolean isExact = (find.location == 0 && find.length == nameLen);
130			if (find.location == kCFNotFound) {
131				free(nameBuf);
132				safe_CFRelease(&nameRef);
133				safe_CFRelease(&cert);
134				safe_CFRelease(&candidate);
135				continue; // no match
136			}
137			if (identityRef) {	// got two matches
138				if (exactMatch && !isExact)	{	// prior is better; ignore this one
139					free(nameBuf);
140					safe_CFRelease(&nameRef);
141					safe_CFRelease(&cert);
142					safe_CFRelease(&candidate);
143					continue;
144				}
145				if (exactMatch == isExact) {	// same class of match
146					if (CFEqual(identityRef, candidate)) {	// identities have same cert
147						free(nameBuf);
148						safe_CFRelease(&nameRef);
149						safe_CFRelease(&cert);
150						safe_CFRelease(&candidate);
151						continue;
152					}
153					// ambiguity - must fail
154					sec_error("\"%s\" is ambiguous, matches more than one certificate", identity);
155					free(nameBuf);
156					safe_CFRelease(&nameRef);
157					safe_CFRelease(&cert);
158					safe_CFRelease(&candidate);
159					safe_CFRelease(&identityRef);
160					break;
161				}
162				safe_CFRelease(&identityRef); // about to replace with this one
163			}
164			identityRef = candidate; // currently retained
165			exactMatch = isExact;
166			free(nameBuf);
167			safe_CFRelease(&nameRef);
168		}
169		safe_CFRelease(&cert);
170	}
171
172	safe_CFRelease(&searchRef);
173	safe_CFRelease(&matchRef);
174	if (hashData.Data) {
175		free(hashData.Data);
176	}
177
178	return identityRef;
179}
180
181void printIdentity(SecIdentityRef identity, SecPolicyRef policy, int ordinalValue)
182{
183	OSStatus status;
184	Boolean printHash = TRUE, printName = TRUE;
185	SecCertificateRef cert = NULL;
186
187	status = SecIdentityCopyCertificate(identity, &cert);
188	if (!status)
189	{
190		CSSM_DATA certData = { 0, nil };
191		(void) SecCertificateGetData(cert, &certData);
192		fprintf(stdout, "%3d) ", ordinalValue);
193		if (printHash) {
194			uint8 sha1_hash[20];
195			CSSM_DATA digest;
196			digest.Length = sizeof(sha1_hash);
197			digest.Data = sha1_hash;
198			if (SecDigestGetData(CSSM_ALGID_SHA1, &digest, &certData) == CSSM_OK) {
199				unsigned int i;
200				uint32 len = digest.Length;
201				uint8 *cp = digest.Data;
202				for(i=0; i<len; i++) {
203					fprintf(stdout, "%02X", ((unsigned char *)cp)[i]);
204				}
205			} else {
206				fprintf(stdout, "!----- unable to get SHA-1 digest -----!");
207			}
208		}
209		if (printName) {
210			char *nameBuf = NULL;
211			CFStringRef nameRef = NULL;
212			status = SecCertificateInferLabel(cert, &nameRef);
213			CFIndex nameLen = (nameRef) ? CFStringGetLength(nameRef) : 0;
214			if (nameLen > 0) {
215				CFIndex bufLen = 1 + CFStringGetMaximumSizeForEncoding(nameLen, kCFStringEncodingUTF8);
216				nameBuf = (char *)malloc(bufLen);
217				if (!CFStringGetCString(nameRef, nameBuf, bufLen-1, kCFStringEncodingUTF8))
218					nameBuf[0]=0;
219			}
220			fprintf(stdout, " \"%s\"", (nameBuf && nameBuf[0] != 0) ? nameBuf : "<unknown>");
221			if (nameBuf)
222				free(nameBuf);
223			safe_CFRelease(&nameRef);
224		}
225
226		// Default to X.509 Basic if no policy was specified
227		if (!policy) {
228			SecPolicySearchRef policySearch = NULL;
229			if (SecPolicySearchCreate(CSSM_CERT_X_509v3, &CSSMOID_APPLE_X509_BASIC, NULL, &policySearch)==noErr) {
230				SecPolicySearchCopyNext(policySearch, &policy);
231			}
232			safe_CFRelease(&policySearch);
233		} else {
234			CFRetain(policy);
235		}
236
237		// Create the trust reference, given policy and certificates
238		SecTrustRef trust = nil;
239		SecTrustResultType trustResult = kSecTrustResultInvalid;
240		OSStatus trustResultCode = noErr;
241		CFMutableArrayRef certificates = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
242		if (certificates) {
243			CFArrayAppendValue(certificates, cert);
244		}
245		status = SecTrustCreateWithCertificates((CFArrayRef)certificates, policy, &trust);
246		if (!status) {
247			status = SecTrustEvaluate(trust, &trustResult);
248		}
249		if (trustResult != kSecTrustResultInvalid) {
250			status = SecTrustGetCssmResultCode(trust, &trustResultCode);
251		}
252		if (trustResultCode != noErr) {
253			fprintf(stdout, " (%s)\n", 	cssmErrorString(trustResultCode));
254		} else {
255			fprintf(stdout, "\n");
256		}
257		safe_CFRelease(&trust);
258		safe_CFRelease(&policy);
259		safe_CFRelease(&certificates);
260	}
261	safe_CFRelease(&cert);
262}
263
264void
265do_identity_search_with_policy(CFTypeRef keychainOrArray,
266							   const char *name,
267							   const CSSM_OID* oidPtr,
268							   CSSM_KEYUSE keyUsage,
269							   Boolean client,
270							   Boolean validOnly)
271{
272	// set up SMIME options with provided data
273	CE_KeyUsage ceKeyUsage = 0;
274	if (keyUsage & CSSM_KEYUSE_SIGN) ceKeyUsage |= CE_KU_DigitalSignature;
275	if (keyUsage & CSSM_KEYUSE_ENCRYPT) ceKeyUsage |= CE_KU_KeyEncipherment;
276	CSSM_APPLE_TP_SMIME_OPTIONS smimeOpts = {
277		CSSM_APPLE_TP_SMIME_OPTS_VERSION,				// Version
278		ceKeyUsage,										// IntendedUsage
279		name ? strlen(name) : 0,						// SenderEmailLen
280		name											// SenderEmail
281	};
282	CSSM_DATA smimeValue = { sizeof(smimeOpts), (uint8*)&smimeOpts };
283
284	// set up SSL options with provided data
285	CSSM_APPLE_TP_SSL_OPTIONS sslOpts = {
286		CSSM_APPLE_TP_SSL_OPTS_VERSION,					// Version
287		(name && !client) ? strlen(name) : 0,			// ServerNameLen
288		(client) ? NULL : name,							// ServerName
289		(client) ? CSSM_APPLE_TP_SSL_CLIENT : 0			// Flags
290	};
291	CSSM_DATA sslValue = { sizeof(sslOpts), (uint8*)&sslOpts };
292
293	// get a policy ref for the specified policy OID
294	OSStatus status = noErr;
295	SecPolicyRef policy = NULL;
296	SecPolicySearchRef policySearch = NULL;
297	status = SecPolicySearchCreate(CSSM_CERT_X_509v3, oidPtr, NULL, &policySearch);
298	if (!status)
299		status = SecPolicySearchCopyNext(policySearch, &policy);
300
301	CSSM_DATA *policyValue = NULL;
302	const char *policyName = "<unknown>";
303
304	if (compareOids(oidPtr, &CSSMOID_APPLE_TP_SMIME)) {
305		policyName = "S/MIME";
306		policyValue = &smimeValue;
307	}
308	else if (compareOids(oidPtr, &CSSMOID_APPLE_TP_SSL)) {
309		if (client)
310			policyName = "SSL (client)";
311		else
312			policyName = "SSL (server)";
313		policyValue = &sslValue;
314	}
315	else if (compareOids(oidPtr, &CSSMOID_APPLE_TP_EAP)) {
316		policyName = "EAP";
317	}
318	else if (compareOids(oidPtr, &CSSMOID_APPLE_TP_IP_SEC)) {
319		policyName = "IPsec";
320	}
321	else if (compareOids(oidPtr, &CSSMOID_APPLE_TP_ICHAT)) {
322		policyName = "iChat";
323	}
324	else if (compareOids(oidPtr, &CSSMOID_APPLE_TP_CODE_SIGNING)) {
325		policyName = "Code Signing";
326	}
327	else if (compareOids(oidPtr, &CSSMOID_APPLE_X509_BASIC)) {
328		policyName = "X.509 Basic";
329	}
330	else if (compareOids(oidPtr, &CSSMOID_APPLE_TP_MACAPPSTORE_RECEIPT)) {
331		policyName = "Mac App Store Receipt";
332	}
333	else if (compareOids(oidPtr, &CSSMOID_APPLE_TP_APPLEID_SHARING)) {
334		policyName = "AppleID Sharing";
335	}
336
337	// set the policy's value, if there is one (this is specific to certain policies)
338	if (policy && policyValue)
339		status = SecPolicySetValue(policy, policyValue);
340
341	CFStringRef idStr = (name) ? CFStringCreateWithCStringNoCopy(NULL, name, kCFStringEncodingUTF8, kCFAllocatorNull) : NULL;
342	SecIdentitySearchRef searchRef = NULL;
343	int identityCount = 0;
344
345	if (!validOnly) {
346		// create an identity search, specifying all identities (i.e. returnOnlyValidIdentities=FALSE)
347		// this should return all identities which match the policy and key usage, regardless of validity
348		fprintf(stdout, "\nPolicy: %s\n", policyName);
349		fprintf(stdout, "  Matching identities\n");
350		status = SecIdentitySearchCreateWithPolicy(policy, idStr, keyUsage, keychainOrArray, FALSE, &searchRef);
351		if (!status)
352		{
353			SecIdentityRef identityRef = NULL;
354			while (SecIdentitySearchCopyNext(searchRef, &identityRef) == noErr)
355			{
356				identityCount++;
357				printIdentity(identityRef, policy, identityCount);
358				safe_CFRelease(&identityRef);
359			}
360			safe_CFRelease(&searchRef);
361		}
362		fprintf(stdout, "     %d identities found\n\n", identityCount);
363	}
364
365	// create a second identity search, specifying only valid identities (i.e. returnOnlyValidIdentities=TRUE)
366	// this should return only valid identities for the policy.
367	identityCount = 0;
368	if (!validOnly) {
369		fprintf(stdout, "  Valid identities only\n");
370	}
371	status = SecIdentitySearchCreateWithPolicy(policy, idStr, keyUsage, keychainOrArray, TRUE, &searchRef);
372	if (!status)
373	{
374		SecIdentityRef identityRef = NULL;
375		while (SecIdentitySearchCopyNext(searchRef, &identityRef) == noErr)
376		{
377			identityCount++;
378			printIdentity(identityRef, policy, identityCount);
379			safe_CFRelease(&identityRef);
380		}
381		safe_CFRelease(&searchRef);
382	}
383	fprintf(stdout, "     %d valid identities found\n", identityCount);
384
385	safe_CFRelease(&idStr);
386	safe_CFRelease(&policy);
387	safe_CFRelease(policySearch);
388}
389
390void
391do_system_identity_search(CFStringRef domain)
392{
393    SecIdentityRef identity = NULL;
394    OSStatus status = SecIdentityCopySystemIdentity(domain, &identity, NULL);
395    if (CFEqual(domain, kSecIdentityDomainDefault)) {
396    	fprintf(stdout, "\n  System default identity\n");
397    } else if (CFEqual(domain, kSecIdentityDomainKerberosKDC)) {
398    	fprintf(stdout, "\n  System Kerberos KDC identity\n");
399    }
400    if (!status && identity) {
401        SecPolicyRef policy = NULL;
402        SecPolicySearchRef policySearch = NULL;
403        if (SecPolicySearchCreate(CSSM_CERT_X_509v3, &CSSMOID_APPLE_X509_BASIC, NULL, &policySearch) == noErr) {
404            if (SecPolicySearchCopyNext(policySearch, &policy) == noErr) {
405                printIdentity(identity, policy, 1);
406                CFRelease(policy);
407            }
408            safe_CFRelease(&policySearch);
409        }
410        safe_CFRelease(&identity);
411    }
412}
413
414int
415do_find_identities(CFTypeRef keychainOrArray, const char *name, unsigned int policyFlags, Boolean validOnly)
416{
417	int result = 0;
418
419	if (name) {
420		fprintf(stdout, "Looking for identities matching \"%s\"\n", name);
421    }
422	if (policyFlags & (1 << 0))
423		do_identity_search_with_policy(keychainOrArray, name, &CSSMOID_APPLE_TP_SSL, CSSM_KEYUSE_SIGN, TRUE, validOnly);
424	if (policyFlags & (1 << 1))
425		do_identity_search_with_policy(keychainOrArray, name, &CSSMOID_APPLE_TP_SSL, CSSM_KEYUSE_SIGN, FALSE, validOnly);
426	if (policyFlags & (1 << 2))
427		do_identity_search_with_policy(keychainOrArray, name, &CSSMOID_APPLE_TP_SMIME, CSSM_KEYUSE_SIGN, TRUE, validOnly);
428	if (policyFlags & (1 << 3))
429		do_identity_search_with_policy(keychainOrArray, name, &CSSMOID_APPLE_TP_EAP, CSSM_KEYUSE_SIGN, TRUE, validOnly);
430	if (policyFlags & (1 << 4))
431		do_identity_search_with_policy(keychainOrArray, name, &CSSMOID_APPLE_TP_IP_SEC, CSSM_KEYUSE_SIGN, TRUE, validOnly);
432	if (policyFlags & (1 << 5))
433		do_identity_search_with_policy(keychainOrArray, name, &CSSMOID_APPLE_TP_ICHAT, CSSM_KEYUSE_SIGN, TRUE, validOnly);
434	if (policyFlags & (1 << 6))
435		do_identity_search_with_policy(keychainOrArray, name, &CSSMOID_APPLE_TP_CODE_SIGNING, CSSM_KEYUSE_SIGN, TRUE, validOnly);
436	if (policyFlags & (1 << 7))
437		do_identity_search_with_policy(keychainOrArray, name, &CSSMOID_APPLE_X509_BASIC, CSSM_KEYUSE_SIGN, TRUE, validOnly);
438
439	if (policyFlags & (1 << 8))
440		do_system_identity_search(kSecIdentityDomainDefault);
441	if (policyFlags & (1 << 9))
442		do_system_identity_search(kSecIdentityDomainKerberosKDC);
443	if (policyFlags & (1 << 10))
444		do_identity_search_with_policy(keychainOrArray, name, &CSSMOID_APPLE_TP_APPLEID_SHARING, CSSM_KEYUSE_SIGN, FALSE, validOnly);
445	if (policyFlags & (1 << 11))
446		do_identity_search_with_policy(keychainOrArray, name, &CSSMOID_APPLE_TP_MACAPPSTORE_RECEIPT, CSSM_KEYUSE_SIGN, TRUE, validOnly);
447
448	return result;
449}
450
451int
452keychain_find_identity(int argc, char * const *argv)
453{
454	int ch, result = 0;
455	unsigned int policyFlags = 0;
456	const char *name = NULL;
457	Boolean validOnly = FALSE;
458	CFTypeRef keychainOrArray = NULL;
459
460	/*
461	 *	"    -p  Specify policy to evaluate (multiple -p options are allowed)\n"
462	 *	"    -s  Specify optional policy-specific string (e.g. a DNS hostname for SSL,\n"
463	 *  "        or RFC822 email address for S/MIME)\n"
464	 *	"    -v  Show valid identities only (default is to show all identities)\n"
465	 */
466
467	while ((ch = getopt(argc, argv, "hp:s:v")) != -1)
468	{
469		switch  (ch)
470		{
471			case 'p':
472				if (optarg != NULL) {
473					if      (!strcmp(optarg, "ssl-client"))
474						policyFlags |= 1 << 0;
475					else if (!strcmp(optarg, "ssl-server"))
476						policyFlags |= 1 << 1;
477					else if (!strcmp(optarg, "smime"))
478						policyFlags |= 1 << 2;
479					else if (!strcmp(optarg, "eap"))
480						policyFlags |= 1 << 3;
481					else if (!strcmp(optarg, "ipsec"))
482						policyFlags |= 1 << 4;
483					else if (!strcmp(optarg, "ichat"))
484						policyFlags |= 1 << 5;
485					else if (!strcmp(optarg, "codesigning"))
486						policyFlags |= 1 << 6;
487					else if (!strcmp(optarg, "basic"))
488						policyFlags |= 1 << 7;
489					else if (!strcmp(optarg, "sys-default"))
490						policyFlags |= 1 << 8;
491					else if (!strcmp(optarg, "sys-kerberos-kdc"))
492						policyFlags |= 1 << 9;
493					else if (!strcmp(optarg, "appleID"))
494						policyFlags |= 1 << 10;
495					else if (!strcmp(optarg, "macappstore"))
496						policyFlags |= 1 << 11;
497					else {
498						result = 2; /* @@@ Return 2 triggers usage message. */
499						goto cleanup;
500					}
501				}
502				break;
503			case 's':
504				name = optarg;
505				break;
506			case 'v':
507				validOnly = TRUE;
508				break;
509			case '?':
510			default:
511				result = 2; /* @@@ Return 2 triggers usage message. */
512				goto cleanup;
513		}
514	}
515
516	if (!policyFlags)
517		policyFlags |= 1 << 7; /* default to basic policy if none specified */
518
519	argc -= optind;
520	argv += optind;
521
522    keychainOrArray = keychain_create_array(argc, argv);
523
524	result = do_find_identities(keychainOrArray, name, policyFlags, validOnly);
525
526cleanup:
527	safe_CFRelease(&keychainOrArray);
528
529	return result;
530}
531
532