1/*
2 * Copyright (c) 2004 Apple Computer, 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 * identPicker.cpp - Given a keychain, select from possible multiple
25 *		     SecIdentityRefs via stdio UI, and cook up a
26 *		     CFArray containing that identity and all certs needed
27 *		     for cert verification by an SSL peer. The resulting
28 *		     CFArrayRef is suitable for passing to SSLSetCertificate().
29 */
30
31#include "identPicker.h"
32#include <sys/param.h>
33#include <string.h>
34#include <stdio.h>
35#include <ctype.h>
36
37/*
38 * Safe gets().
39 * -- guaranteed no buffer overflow
40 * -- guaranteed NULL-terminated string
41 * -- handles empty string (i.e., response is just CR) properly
42 */
43static void getString(
44	char *buf,
45	unsigned bufSize)
46{
47    unsigned dex;
48    char c;
49    char *cp = buf;
50
51    for(dex=0; dex<bufSize-1; dex++) {
52	c = getchar();
53	if(!isprint(c)) {
54	    break;
55	}
56	switch(c) {
57	    case '\n':
58	    case '\r':
59		goto done;
60	    default:
61		*cp++ = c;
62	}
63    }
64done:
65    *cp = '\0';
66}
67
68/*
69 * Obtain the printable name of a SecKeychainItemRef as a C string.
70 * Caller must free() the result.
71 */
72static char *kcItemPrintableName(
73    SecKeychainItemRef certRef)
74{
75    char *crtn = NULL;
76
77    /* just search for the one attr we want */
78    UInt32 tag = kSecLabelItemAttr;
79    SecKeychainAttributeInfo attrInfo;
80    attrInfo.count = 1;
81    attrInfo.tag = &tag;
82    attrInfo.format = NULL;
83    SecKeychainAttributeList *attrList = NULL;
84    SecKeychainAttribute *attr = NULL;
85
86    OSStatus ortn = SecKeychainItemCopyAttributesAndData(
87	(SecKeychainItemRef)certRef,
88	&attrInfo,
89	NULL,			// itemClass
90	&attrList,
91	NULL,			// length - don't need the data
92	NULL);			// outData
93    if(ortn) {
94	cssmPerror("SecKeychainItemCopyAttributesAndData", ortn);
95	/* may want to be a bit more robust here, but this should
96	 * never happen */
97	return strdup("Unnamed KeychainItem");
98    }
99    /* subsequent errors to errOut: */
100
101    if((attrList == NULL) || (attrList->count != 1)) {
102	printf("***Unexpected result fetching label attr\n");
103	crtn = strdup("Unnamed KeychainItem");
104	goto errOut;
105    }
106    /* We're assuming 8-bit ASCII attribute data here... */
107    attr = attrList->attr;
108    crtn = (char *)malloc(attr->length + 1);
109    memmove(crtn, attr->data, attr->length);
110    crtn[attr->length] = '\0';
111
112errOut:
113    SecKeychainItemFreeAttributesAndData(attrList, NULL);
114    return crtn;
115}
116
117/*
118 * Get the final term of a keychain's path as a C string. Caller must free()
119 * the result.
120 */
121static char *kcFileName(
122	SecKeychainRef kcRef)
123{
124    char fullPath[MAXPATHLEN + 1];
125    OSStatus ortn;
126    UInt32 pathLen = MAXPATHLEN;
127
128    ortn = SecKeychainGetPath(kcRef,  &pathLen, fullPath);
129    if(ortn) {
130	cssmPerror("SecKeychainGetPath", ortn);
131	return strdup("orphan keychain");
132    }
133
134    /* NULL terminate the path string and search for final '/' */
135    fullPath[pathLen] = '\0';
136    char *lastSlash = NULL;
137    char *thisSlash = fullPath;
138    do {
139	thisSlash = strchr(thisSlash, '/');
140	if(thisSlash == NULL) {
141	    /* done */
142	    break;
143	}
144	thisSlash++;
145	lastSlash = thisSlash;
146    } while(thisSlash != NULL);
147    if(lastSlash == NULL) {
148	/* no slashes, odd, but handle it */
149	return strdup(fullPath);
150    }
151    else {
152	return strdup(lastSlash);
153    }
154}
155
156/*
157 * Determine if specified SecCertificateRef is a self-signed cert.
158 * We do this by comparing the subject and issuerr names; no cryptographic
159 * verification is performed.
160 *
161 * Returns true if the cert appears to be a root.
162 */
163static bool isCertRefRoot(
164	SecCertificateRef certRef)
165{
166    /* just search for the two attrs we want */
167    UInt32 tags[2] = {kSecSubjectItemAttr, kSecIssuerItemAttr};
168    SecKeychainAttributeInfo attrInfo;
169    attrInfo.count = 2;
170    attrInfo.tag = tags;
171    attrInfo.format = NULL;
172    SecKeychainAttributeList *attrList = NULL;
173    SecKeychainAttribute *attr1 = NULL;
174    SecKeychainAttribute *attr2 = NULL;
175    bool brtn = false;
176
177    OSStatus ortn = SecKeychainItemCopyAttributesAndData(
178	(SecKeychainItemRef)certRef,
179	&attrInfo,
180	NULL,			// itemClass
181	&attrList,
182	NULL,			// length - don't need the data
183	NULL);			// outData
184    if(ortn) {
185	cssmPerror("SecKeychainItemCopyAttributesAndData", ortn);
186	/* may want to be a bit more robust here, but this should
187	 * never happen */
188	return false;
189    }
190    /* subsequent errors to errOut: */
191
192    if((attrList == NULL) || (attrList->count != 2)) {
193	printf("***Unexpected result fetching label attr\n");
194	goto errOut;
195    }
196
197    /* rootness is just byte-for-byte compare of the two names */
198    attr1 = &attrList->attr[0];
199    attr2 = &attrList->attr[1];
200    if(attr1->length == attr2->length) {
201	if(memcmp(attr1->data, attr2->data, attr1->length) == 0) {
202	    brtn = true;
203	}
204    }
205errOut:
206    SecKeychainItemFreeAttributesAndData(attrList, NULL);
207    return brtn;
208}
209
210
211/*
212 * Given a SecIdentityRef, do our best to construct a complete, ordered, and
213 * verified cert chain, returning the result in a CFArrayRef. The result is
214 * suitable for use when calling SSLSetCertificate().
215 */
216static OSStatus completeCertChain(
217	SecIdentityRef 		identity,
218	SecCertificateRef	trustedAnchor,	// optional additional trusted anchor
219	bool			includeRoot, 	// include the root in outArray
220	CFArrayRef		*outArray)	// created and RETURNED
221{
222    CFMutableArrayRef 			certArray;
223    SecTrustRef					secTrust = NULL;
224    SecPolicyRef				policy = NULL;
225    SecPolicySearchRef			policySearch = NULL;
226    SecTrustResultType			secTrustResult;
227    CSSM_TP_APPLE_EVIDENCE_INFO *dummyEv;			// not used
228    CFArrayRef					certChain = NULL;   // constructed chain
229    CFIndex 					numResCerts;
230
231    certArray = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
232    CFArrayAppendValue(certArray, identity);
233
234    /*
235     * Case 1: identity is a root; we're done. Note that this case
236     * overrides the includeRoot argument.
237     */
238    SecCertificateRef certRef;
239    OSStatus ortn = SecIdentityCopyCertificate(identity, &certRef);
240    if(ortn) {
241	/* should never happen */
242	cssmPerror("SecIdentityCopyCertificate", ortn);
243	return ortn;
244    }
245    bool isRoot = isCertRefRoot(certRef);
246    if(isRoot) {
247	*outArray = certArray;
248	CFRelease(certRef);
249	return noErr;
250    }
251
252    /*
253     * Now use SecTrust to get a complete cert chain, using all of the
254     * user's keychains to look for intermediate certs.
255     * NOTE this does NOT handle root certs which are not in the system
256     * root cert DB. (The above case, where the identity is a root cert, does.)
257     */
258    CFMutableArrayRef subjCerts = CFArrayCreateMutable(NULL, 1, &kCFTypeArrayCallBacks);
259    CFArraySetValueAtIndex(subjCerts, 0, certRef);
260
261    /* the array owns the subject cert ref now */
262    CFRelease(certRef);
263
264    /* Get a SecPolicyRef for generic X509 cert chain verification */
265    ortn = SecPolicySearchCreate(CSSM_CERT_X_509v3,
266	    &CSSMOID_APPLE_X509_BASIC,
267	    NULL,				// value
268	    &policySearch);
269    if(ortn) {
270	cssmPerror("SecPolicySearchCreate", ortn);
271	goto errOut;
272    }
273    ortn = SecPolicySearchCopyNext(policySearch, &policy);
274    if(ortn) {
275	cssmPerror("SecPolicySearchCopyNext", ortn);
276	goto errOut;
277    }
278
279    /* build a SecTrustRef for specified policy and certs */
280    ortn = SecTrustCreateWithCertificates(subjCerts,
281	    policy, &secTrust);
282    if(ortn) {
283	cssmPerror("SecTrustCreateWithCertificates", ortn);
284	goto errOut;
285    }
286
287    if(trustedAnchor) {
288	/*
289	 * Tell SecTrust to trust this one in addition to the current
290	 * trusted system-wide anchors.
291	 */
292	CFMutableArrayRef newAnchors;
293	CFArrayRef currAnchors;
294
295	ortn = SecTrustCopyAnchorCertificates(&currAnchors);
296	if(ortn) {
297	    /* should never happen */
298	    cssmPerror("SecTrustCopyAnchorCertificates", ortn);
299	    goto errOut;
300	}
301	newAnchors = CFArrayCreateMutableCopy(NULL,
302	    CFArrayGetCount(currAnchors) + 1,
303	    currAnchors);
304	CFRelease(currAnchors);
305	CFArrayAppendValue(newAnchors, trustedAnchor);
306	ortn = SecTrustSetAnchorCertificates(secTrust, newAnchors);
307	CFRelease(newAnchors);
308	if(ortn) {
309	    cssmPerror("SecTrustSetAnchorCertificates", ortn);
310	    goto errOut;
311	}
312    }
313    /* evaluate: GO */
314    ortn = SecTrustEvaluate(secTrust, &secTrustResult);
315    if(ortn) {
316	cssmPerror("SecTrustEvaluate", ortn);
317	goto errOut;
318    }
319    switch(secTrustResult) {
320	case kSecTrustResultUnspecified:
321	    /* cert chain valid, no special UserTrust assignments */
322	case kSecTrustResultProceed:
323	    /* cert chain valid AND user explicitly trusts this */
324	    break;
325	default:
326	    /*
327	     * Cert chain construction failed.
328	     * Just go with the single subject cert we were given.
329	     */
330	    printf("***Warning: could not construct completed cert chain\n");
331	    ortn = noErr;
332	    goto errOut;
333    }
334
335    /* get resulting constructed cert chain */
336    ortn = SecTrustGetResult(secTrust, &secTrustResult, &certChain, &dummyEv);
337    if(ortn) {
338	cssmPerror("SecTrustEvaluate", ortn);
339	goto errOut;
340    }
341
342    /*
343     * Copy certs from constructed chain to our result array, skipping
344     * the leaf (which is already there, as a SecIdentityRef) and possibly
345     * a root.
346     */
347    numResCerts = CFArrayGetCount(certChain);
348    if(numResCerts < 2) {
349	/*
350	 * Can't happen: if subject was a root, we'd already have returned.
351	 * If chain doesn't verify to a root, we'd have bailed after
352	 * SecTrustEvaluate().
353	 */
354	printf("***sslCompleteCertChain screwup: numResCerts %d\n",
355		(int)numResCerts);
356	ortn = noErr;
357	goto errOut;
358    }
359    if(!includeRoot) {
360	/* skip the last (root) cert) */
361	numResCerts--;
362    }
363    for(CFIndex dex=1; dex<numResCerts; dex++) {
364	certRef = (SecCertificateRef)CFArrayGetValueAtIndex(certChain, dex);
365	CFArrayAppendValue(certArray, certRef);
366    }
367errOut:
368    /* clean up */
369    if(secTrust) {
370	CFRelease(secTrust);
371    }
372    if(subjCerts) {
373	CFRelease(subjCerts);
374    }
375    if(policy) {
376	CFRelease(policy);
377    }
378    if(policySearch) {
379	CFRelease(policySearch);
380    }
381    *outArray = certArray;
382    return ortn;
383}
384
385
386/*
387 * Given an array of SecIdentityRefs:
388 *  -- display a printable name of each identity's cert;
389 *  -- prompt user to select which one to use;
390 *
391 * Returns CFIndex of desired identity. A return of <0 indicates
392 * "none - abort".
393 */
394static CFIndex pickIdent(
395	CFArrayRef idArray)
396{
397    CFIndex count = CFArrayGetCount(idArray);
398    CFIndex dex;
399    OSStatus ortn;
400
401    if(count == 0) {
402	    printf("***sslIdentPicker screwup: no identities found\n");
403	    return -1;
404    }
405    for(dex=0; dex<count; dex++) {
406	SecIdentityRef idRef = (SecIdentityRef)CFArrayGetValueAtIndex(idArray, dex);
407	SecCertificateRef certRef;
408	ortn = SecIdentityCopyCertificate(idRef, &certRef);
409	if(ortn) {
410	    /* should never happen */
411	    cssmPerror("SecIdentityCopyCertificate", ortn);
412	    return -1;
413	}
414
415	/* get printable name of cert and the keychain it's in */
416	char *certLabel = kcItemPrintableName((SecKeychainItemRef)certRef);
417	SecKeychainRef kcRef;
418	char *kcLabel;
419	ortn = SecKeychainItemCopyKeychain((SecKeychainItemRef)certRef, &kcRef);
420	if(ortn) {
421	    cssmPerror("SecKeychainItemCopyKeychain", ortn);
422	    kcLabel = "Unnamed keychain";
423	}
424	else {
425	    kcLabel = kcFileName(kcRef);
426	}
427	printf("[%d] keychain : %s\n", (int)dex, kcLabel);
428	printf("    cert     : %s\n", certLabel);
429	free(certLabel);
430	if(ortn == noErr) {
431	    free(kcLabel);
432	}
433	CFRelease(certRef);
434    }
435
436    while(1) {
437	fpurge(stdin);
438	printf("\nEnter Certificate number or CR to quit : ");
439	fflush(stdout);
440	char resp[64];
441	getString(resp, sizeof(resp));
442	if(resp[0] == '\0') {
443	    return -1;
444	}
445	int ires = atoi(resp);
446	if((ires >= 0) && (ires < count)) {
447	    return (CFIndex)ires;
448	}
449	printf("***Invalid entry. Type a number between 0 and %d\n",
450	    (int)(count-1));
451    }
452    return -1;
453}
454
455OSStatus simpleIdentPicker(
456    SecKeychainRef	kcRef,			// NULL means use default list
457    SecIdentityRef	*ident)			// RETURNED
458{
459    OSStatus 		ortn;
460    CFMutableArrayRef	idArray = NULL;		// holds all SecIdentityRefs found
461
462    /* Search for all identities */
463    *ident = NULL;
464    SecIdentitySearchRef srchRef = nil;
465    ortn = SecIdentitySearchCreate(kcRef,
466	0,				// keyUsage - any
467	&srchRef);
468    if(ortn) {
469	cssmPerror("SecIdentitySearchCreate", (CSSM_RETURN)ortn);
470	printf("Cannot find signing key in keychain.\n");
471	return ortn;
472    }
473
474    /* get all identities, stuff them into idArray */
475    SecIdentityRef identity = nil;
476    idArray = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
477    do {
478	ortn = SecIdentitySearchCopyNext(srchRef, &identity);
479	if(ortn != noErr) {
480	    break;
481	}
482	CFArrayAppendValue(idArray, identity);
483
484	/* the array has the retain count we need */
485	CFRelease(identity);
486    } while(ortn == noErr);
487
488    switch(ortn) {
489	case errSecItemNotFound:
490	    if(CFArrayGetCount(idArray) == 0) {
491		printf("No signing keys found in keychain.\n");
492		return errSecItemNotFound;
493	    }
494	    else {
495		/* found at least one; proceed */
496		break;
497	    }
498	default:
499	    cssmPerror("SecIdentitySearchCopyNext", (CSSM_RETURN)ortn);
500	    printf("Cannot find signing key in keychain.\n");
501	    return ortn;
502    }
503
504    /*
505     * If there is just one, use it without asking
506     */
507    CFIndex whichId;
508    if(CFArrayGetCount(idArray) == 1) {
509	whichId = 0;
510    }
511    else {
512	whichId = pickIdent(idArray);
513	if(whichId < 0) {
514	    return CSSMERR_CSSM_USER_CANCELED;
515	}
516    }
517
518    /* keep this one, free the rest */
519    identity = (SecIdentityRef)CFArrayGetValueAtIndex(idArray, whichId);
520    CFRetain(identity);
521    CFRelease(idArray);
522    *ident = identity;
523    return noErr;
524}
525
526OSStatus identPicker(
527    SecKeychainRef	kcRef,		// NULL means use default list
528    SecCertificateRef	trustedAnchor,	// optional additional trusted anchor
529    bool		includeRoot,	// true --> root is appended to outArray
530					// false --> root not included
531    CFArrayRef		*outArray)	// created and RETURNED
532{
533    OSStatus 			ortn;
534    SecIdentityRef		identity;
535
536    ortn = simpleIdentPicker(kcRef, &identity);
537    if(ortn) {
538	return ortn;
539    }
540    return completeCertChain(identity, trustedAnchor, includeRoot, outArray);
541}
542
543