identPicker.cpp   [plain text]


/*
 * Copyright (c) 2003-2008 Apple Inc. All Rights Reserved.
 * 
 * The contents of this file constitute Original Code as defined in and are
 * subject to the Apple Public Source License Version 1.2 (the 'License').
 * You may not use this file except in compliance with the License. Please 
 * obtain a copy of the License at http://www.apple.com/publicsource and 
 * read it before using this file.
 * 
 * This Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER 
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, 
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. 
 * Please see the License for the specific language governing rights and 
 * limitations under the License.
 */

/*
 * identPicker.cp - Given a keychain, select from possible multiple
 * 				   	SecIdentityRefs via stdio UI, and cook up a 
 *				   	CFArray containing that identity and all certs needed
 *				   	for cert verification by an SSL peer. The resulting
 *				   	CFArrayRef is suitable for passing to SSLSetCertificate().
 */
 
#include "identPicker.h"
#include "sslAppUtils.h"
#include <sys/param.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>

/* 
 * Safe gets().
 * -- guaranteed no buffer overflow
 * -- guaranteed NULL-terminated string
 * -- handles empty string (i.e., response is just CR) properly
 */
void getString(
	char *buf,
	unsigned bufSize)
{
	unsigned dex;
	char c;
	char *cp = buf;
	
	for(dex=0; dex<bufSize-1; dex++) {
		c = getchar();
		if(!isprint(c)) {
			break;
		}
		switch(c) {
			case '\n':
			case '\r':
				goto done;
			default:
				*cp++ = c;
		}
	}
done:
	*cp = '\0';
}

/*
 * Obtain the printable name of a SecKeychainItemRef as a C string.
 * Caller must free() the result.
 */
char *kcItemPrintableName(
	SecKeychainItemRef itemRef)
{
	char *crtn = NULL;

	/* just search for the one attr we want */
	UInt32 tag = kSecLabelItemAttr;
	SecKeychainAttributeInfo attrInfo;
	attrInfo.count = 1;
	attrInfo.tag = &tag;
	attrInfo.format = NULL;
	SecKeychainAttributeList *attrList = NULL;
	SecKeychainAttribute *attr = NULL;
	
	OSStatus ortn = SecKeychainItemCopyAttributesAndData(
		itemRef, 
		&attrInfo,
		NULL,			// itemClass
		&attrList, 
		NULL,			// length - don't need the data
		NULL);			// outData
	if(ortn) {
		cssmPerror("SecKeychainItemCopyAttributesAndData", ortn);
		/* may want to be a bit more robust here, but this should
		 * never happen */
		return strdup("Unnamed KeychainItem");
	}
	/* subsequent errors to errOut: */
	
	if((attrList == NULL) || (attrList->count != 1)) {
		printf("***Unexpected result fetching label attr\n");
		crtn = strdup("Unnamed KeychainItem");
		goto errOut;
	}
	/* We're assuming 8-bit ASCII attribute data here... */
	attr = attrList->attr;
	crtn = (char *)malloc(attr->length + 1);
	memmove(crtn, attr->data, attr->length);
	crtn[attr->length] = '\0';
	
errOut:
	SecKeychainItemFreeAttributesAndData(attrList, NULL);
	return crtn;
}

/*
 * Get the final term of a keychain's path as a C string. Caller must free() 
 * the result.
 */
char *kcFileName(
	SecKeychainRef kcRef)
{
	char fullPath[MAXPATHLEN + 1];
	OSStatus ortn;
	UInt32 pathLen = MAXPATHLEN;
	
	ortn = SecKeychainGetPath(kcRef,  &pathLen, fullPath);
	if(ortn) {
		cssmPerror("SecKeychainGetPath", ortn);
		return strdup("orphan keychain");
	}
	
	/* NULL terminate the path string and search for final '/' */
	fullPath[pathLen] = '\0';
	char *lastSlash = NULL;
	char *thisSlash = fullPath;
	do {
		thisSlash = strchr(thisSlash, '/');
		if(thisSlash == NULL) {
			/* done */
			break;
		}
		thisSlash++;
		lastSlash = thisSlash;
	} while(thisSlash != NULL);
	if(lastSlash == NULL) {
		/* no slashes, odd, but handle it */
		return strdup(fullPath);
	}
	else {
		return strdup(lastSlash);
	}
}

/* 
 * Obtain the final term of a keychain item's keychain path as a C string. 
 * Caller must free() the result.
 * May well return NULL indicating the item has no keychain (e.g. az floating cert).
 */
char *kcItemKcFileName(SecKeychainItemRef itemRef)
{
	OSStatus ortn;
	SecKeychainRef kcRef = NULL;
	
	ortn = SecKeychainItemCopyKeychain(itemRef, &kcRef);
	if(ortn) {
		return NULL;
	}
	char *rtnStr = kcFileName(kcRef);
	CFRelease(kcRef);
	return rtnStr;
}

/*
 * Given an array of SecIdentityRefs:
 *	-- display a printable name of each identity's cert;
 *  -- prompt user to select which one to use;
 *
 * Returns CFIndex of desired identity. A return of <0 indicates
 * "none - abort".
 */
static CFIndex pickIdent(
	CFArrayRef idArray)
{
	CFIndex count = CFArrayGetCount(idArray);
	CFIndex dex;
	OSStatus ortn;
	
	if(count == 0) {
		printf("***sslIdentPicker screwup: no identities found\n");
		return -1;
	}
	for(dex=0; dex<count; dex++) {
		SecIdentityRef idRef = (SecIdentityRef)CFArrayGetValueAtIndex(idArray, dex);
		SecCertificateRef certRef;
		ortn = SecIdentityCopyCertificate(idRef, &certRef);
		if(ortn) {
			/* should never happen */
			cssmPerror("SecIdentityCopyCertificate", ortn);
			return -1;
		}
		
		/* get printable name of cert and the keychain it's in */
		char *certLabel = kcItemPrintableName((SecKeychainItemRef)certRef);
		SecKeychainRef kcRef;
		char *kcLabel;
		ortn = SecKeychainItemCopyKeychain((SecKeychainItemRef)certRef, &kcRef);
		if(ortn) {
			cssmPerror("SecKeychainItemCopyKeychain", ortn);
			kcLabel = (char *)"Unnamed keychain";
		}
		else {
			kcLabel = kcFileName(kcRef);
		}
		printf("[%ld] keychain : %s\n", dex, kcLabel);
		printf("    cert     : %s\n", certLabel);
		free(certLabel);
		if(ortn == noErr) {
			free(kcLabel);
		}
		CFRelease(certRef);
	}
	
	while(1) {
		fpurge(stdin);
		printf("\nEnter Certificate number or CR to quit : ");
		fflush(stdout);
		char resp[64];
		getString(resp, sizeof(resp));
		if(resp[0] == '\0') {
			return -1;
		}
		int ires = atoi(resp);
		if((ires >= 0) && (ires < count)) {
			return (CFIndex)ires;
		}
		printf("***Invalid entry. Type a number between 0 and %ld\n", 
			count-1);
	}
	return -1;
}

OSStatus sslSimpleIdentPicker(
	SecKeychainRef		kcRef,			// NULL means use default list
	SecIdentityRef		*ident)			// RETURNED
{
	OSStatus 			ortn;
	CFMutableArrayRef	idArray = NULL;			// holds all SecIdentityRefs found
	
	/* Search for all identities */
	*ident = NULL;
	SecIdentitySearchRef srchRef = nil;
	ortn = SecIdentitySearchCreate(kcRef, 
		0,				// keyUsage - any
		&srchRef);
	if(ortn) {
		cssmPerror("SecIdentitySearchCreate", (CSSM_RETURN)ortn);
		printf("Cannot find signing key in keychain.\n");
		return ortn;
	}
	
	/* get all identities, stuff them into idArray */
	SecIdentityRef identity = nil;
	idArray = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
	do {
		ortn = SecIdentitySearchCopyNext(srchRef, &identity);
		if(ortn != noErr) {
			break;
		}
		CFArrayAppendValue(idArray, identity);
		
		/* the array has the retain count we need */
		CFRelease(identity);
	} while(ortn == noErr);
	CFRelease(srchRef);
	
	switch(ortn) {
		case errSecItemNotFound:
			if(CFArrayGetCount(idArray) == 0) {
				printf("No signing keys found in keychain.\n");
				return errSecItemNotFound;
			}
			else {
				/* found at least one; proceed */
				break;
			}
		default:
			cssmPerror("SecIdentitySearchCopyNext", (CSSM_RETURN)ortn);
			printf("Cannot find signing key in keychain.\n");
			return ortn;
	}
	
	/*
	 * If there is just one, use it without asking 
	 */
	CFIndex whichId;
	if(CFArrayGetCount(idArray) == 1) {
		whichId = 0;
	}
	else {
		whichId = pickIdent(idArray);
		if(whichId < 0) {
			return CSSMERR_CSSM_USER_CANCELED;
		}
	}
	
	/* keep this one, free the rest */
	identity = (SecIdentityRef)CFArrayGetValueAtIndex(idArray, whichId);
	CFRetain(identity);
	CFRelease(idArray);
	*ident = identity;
	return noErr;
}

OSStatus sslIdentPicker(
	SecKeychainRef		kcRef,			// NULL means use default list
	SecCertificateRef	trustedAnchor,	// optional additional trusted anchor
	bool				includeRoot,	// true --> root is appended to outArray
										// false --> root not included
	const CSSM_OID		*vfyPolicy,		// optional - if NULL, use SSL
	CFArrayRef			*outArray)		// created and RETURNED
{
	OSStatus 			ortn;
	SecIdentityRef		identity;
	
	ortn = sslSimpleIdentPicker(kcRef, &identity);
	if(ortn) {
		return ortn;
	}
	return sslCompleteCertChain(identity, trustedAnchor, includeRoot, 
		vfyPolicy, outArray);
}