sslKeychain.cpp   [plain text]


/*
 * Copyright (c) 2000-2001 Apple Computer, 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.
 */


/*
	File:		sslKeychain.c

	Contains:	Apple Keychain routines

	Written by:	Doug Mitchell

	Copyright: (c) 1999 by Apple Computer, Inc., all rights reserved.

*/

#include "ssl.h"
#include "sslContext.h"
#include "sslMemory.h"
#include "appleCdsa.h"
#include "sslDebug.h"
#include "sslKeychain.h"
#include "sslUtils.h"
#include <string.h>
#include <assert.h>
#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>
#include <Security/cssm.h>
/* these are to be replaced by Security/Security.h */
#include <Security/SecCertificate.h>
#include <Security/SecKeychainItem.h>
#include <Security/SecKeychain.h>
#include <Security/SecIdentity.h>
#include <Security/SecIdentitySearch.h>
#include <Security/SecKey.h>

#if		ST_MANAGES_TRUSTED_ROOTS
static OSStatus
addCertData(
	SSLContext		*ctx,
	KCItemRef		kcItem,
	CSSM_DATA_PTR	certData,
	Boolean			*goodCert);		/* RETURNED */
#endif	/* ST_MANAGES_TRUSTED_ROOTS */

/*
 * Given an array of certs (as SecIdentityRefs, specified by caller
 * in SSLSetCertificate or SSLSetEncryptionCertificate) and a 
 * destination SSLCertificate:
 *
 * -- free destCerts if we have any
 * -- Get raw cert data, convert to array of SSLCertificates in *destCert 
 * -- validate cert chain
 * -- get pub, priv keys from certRef[0], store in *pubKey, *privKey
 */
 
/* Convert a SecCertificateRef to an SSLCertificate * */
static OSStatus secCertToSslCert(
	SSLContext			*ctx,
	SecCertificateRef 	certRef,
	SSLCertificate		**sslCert)
{
	CSSM_DATA		certData;		// struct is transient, referent owned by 
									//   Sec layer
	OSStatus		ortn;
	SSLCertificate	*thisSslCert = NULL;
	
	ortn = SecCertificateGetData(certRef, &certData);
	if(ortn) {
		sslErrorLog("SecCertificateGetData() returned %d\n", (int)ortn);
		return ortn;
	}
	
	thisSslCert = (SSLCertificate *)sslMalloc(sizeof(SSLCertificate));
	if(thisSslCert == NULL) {
		return memFullErr;
	}
	if(SSLAllocBuffer(thisSslCert->derCert, certData.Length, 
			ctx)) {
		return memFullErr;
	}
	memcpy(thisSslCert->derCert.data, certData.Data, certData.Length);
	thisSslCert->derCert.length = certData.Length;
	*sslCert = thisSslCert;
	return noErr;
}

OSStatus 
parseIncomingCerts(
	SSLContext		*ctx,
	CFArrayRef		certs,
	SSLCertificate	**destCert,		/* &ctx->{localCert,encryptCert} */
	CSSM_KEY_PTR	*pubKey,		/* &ctx->signingPubKey, etc. */
	CSSM_KEY_PTR	*privKey,		/* &ctx->signingPrivKey, etc. */
	CSSM_CSP_HANDLE	*cspHand		/* &ctx->signingKeyCsp, etc. */
	#if		ST_KC_KEYS_NEED_REF
	,
	SecKeychainRef	*privKeyRef)	/* &ctx->signingKeyRef, etc. */
	#else
	)
	#endif	/* ST_KC_KEYS_NEED_REF */
{
	CFIndex				numCerts;
	CFIndex				cert;
	SSLCertificate		*certChain = NULL;
	SSLCertificate		*thisSslCert;
	SecKeychainRef		kcRef;
	OSStatus			ortn;
	SecIdentityRef 		identity;
	SecCertificateRef	certRef;
	SecKeyRef			keyRef;
	CSSM_DATA			certData;
	CSSM_CL_HANDLE		clHand;		// carefully derive from a SecCertificateRef
	CSSM_RETURN			crtn;
	
	assert(ctx != NULL);
	assert(destCert != NULL);		/* though its referent may be NULL */
	assert(pubKey != NULL);
	assert(privKey != NULL);
	assert(cspHand != NULL);
	
	sslDeleteCertificateChain(*destCert, ctx);
	*destCert = NULL;
	*pubKey   = NULL;
	*privKey  = NULL;
	*cspHand  = 0;
	
	if(certs == NULL) {
		sslErrorLog("parseIncomingCerts: NULL incoming cert array\n");
		return errSSLBadCert;
	}
	numCerts = CFArrayGetCount(certs);
	if(numCerts == 0) {
		sslErrorLog("parseIncomingCerts: empty incoming cert array\n");
		return errSSLBadCert;
	}
	
	/* 
	 * Certs[0] is an SecIdentityRef from which we extract subject cert,
	 * privKey, pubKey, and cspHand.
	 *
	 * 1. ensure the first element is a SecIdentityRef.
	 */
	identity = (SecIdentityRef)CFArrayGetValueAtIndex(certs, 0);
	if(identity == NULL) {
		sslErrorLog("parseIncomingCerts: bad cert array (1)\n");
		return paramErr;
	}	
	if(CFGetTypeID(identity) != SecIdentityGetTypeID()) {
		sslErrorLog("parseIncomingCerts: bad cert array (2)\n");
		return paramErr;
	}
	
	/* 
	 * 2. Extract cert, keys, CSP handle and convert to local format. 
	 */
	ortn = SecIdentityCopyCertificate(identity, &certRef);
	if(ortn) {
		sslErrorLog("parseIncomingCerts: bad cert array (3)\n");
		return ortn;
	}
	ortn = secCertToSslCert(ctx, certRef, &thisSslCert);
	if(ortn) {
		sslErrorLog("parseIncomingCerts: bad cert array (4)\n");
		return ortn;
	}
	/* enqueue onto head of cert chain */
	thisSslCert->next = certChain;
	certChain = thisSslCert;

	/* fetch private key from identity */
	ortn = SecIdentityCopyPrivateKey(identity, &keyRef);
	if(ortn) {
		sslErrorLog("parseIncomingCerts: SecIdentityCopyPrivateKey err %d\n",
			(int)ortn);
		return ortn;
	}
	ortn = SecKeyGetCSSMKey(keyRef, (const CSSM_KEY **)privKey);
	if(ortn) {
		sslErrorLog("parseIncomingCerts: SecKeyGetCSSMKey err %d\n",
			(int)ortn);
		return ortn;
	}
	/* FIXME = release keyRef? */
	
	/* obtain public key from cert */
	ortn = SecCertificateGetCLHandle(certRef, &clHand);
	if(ortn) {
		sslErrorLog("parseIncomingCerts: SecCertificateGetCLHandle err %d\n",
			(int)ortn);
		return ortn;
	}
	certData.Data = thisSslCert->derCert.data;
	certData.Length = thisSslCert->derCert.length;
	crtn = CSSM_CL_CertGetKeyInfo(clHand, &certData, pubKey);
	if(crtn) {
		sslErrorLog("parseIncomingCerts: CSSM_CL_CertGetKeyInfo err\n");
		return (OSStatus)crtn;
	}
	
	/* obtain keychain from key, CSP handle from keychain */
	ortn = SecKeychainItemCopyKeychain((SecKeychainItemRef)keyRef, &kcRef);
	if(ortn) {
		sslErrorLog("parseIncomingCerts: SecKeychainItemCopyKeychain err %d\n",
			(int)ortn);
		return ortn;
	}
	ortn = SecKeychainGetCSPHandle(kcRef, cspHand);
	if(ortn) {
		sslErrorLog("parseIncomingCerts: SecKeychainGetCSPHandle err %d\n",
			(int)ortn);
		return ortn;
	}
	
	/* OK, that's the subject cert. Fetch optional remaining certs. */
	/* 
	 * Convert: CFArray of SecCertificateRefs --> chain of SSLCertificates. 
	 * Incoming certs have root last; SSLCertificate chain has root
	 * first.
	 */
	for(cert=1; cert<numCerts; cert++) {
		certRef = (SecCertificateRef)CFArrayGetValueAtIndex(certs, cert);
		if(certRef == NULL) {
			sslErrorLog("parseIncomingCerts: bad cert array (5)\n");
			return paramErr;
		}	
		if(CFGetTypeID(certRef) != SecCertificateGetTypeID()) {
			sslErrorLog("parseIncomingCerts: bad cert array (6)\n");
			return paramErr;
		}
		
		/* Extract cert, convert to local format. 
		*/
		ortn = secCertToSslCert(ctx, certRef, &thisSslCert);
		if(ortn) {
			sslErrorLog("parseIncomingCerts: bad cert array (7)\n");
			return ortn;
		}
		/* enqueue onto head of cert chain */
		thisSslCert->next = certChain;
		certChain = thisSslCert;
	}
	
	/* validate the whole mess, skipping host name verify */
	ortn = sslVerifyCertChain(ctx, *certChain, false);
	if(ortn) {
		goto errOut;
	}
		
	/* SUCCESS */ 
	*destCert = certChain;
	return noErr;
	
errOut:
	/* free certChain, everything in it, other vars, return ortn */
	sslDeleteCertificateChain(certChain, ctx);
	/* FIXME - anything else? */
	return ortn;
}

/*
 * Add Apple built-in root certs to ctx->trustedCerts.
 */
OSStatus addBuiltInCerts	(SSLContextRef		ctx)
{
	#if		ST_MANAGES_TRUSTED_ROOTS
	OSStatus 			ortn;
	KCRef				kc = nil;
	
	ortn = KCDispatch(kKCGetRootCertificateKeychain, &kc);
	if(ortn) {
		sslErrorLog("KCDispatch(kKCGetRootCertificateKeychain) returned %d\n", 
			ortn);
		return ortn;
	}
	return parseTrustedKeychain(ctx, kc);
	#else
	/* nothing for now */
	return noErr;
	#endif	/* ST_MANAGES_TRUSTED_ROOTS */
}

#if		ST_MANAGES_TRUSTED_ROOTS

/*
 * Given an open Keychain:
 * -- Get raw cert data, add to array of CSSM_DATAs in 
 *    ctx->trustedCerts 
 * -- verify that each of these is a valid (self-verifying)
 *    root cert
 * -- add each subject name to acceptableDNList
 */
OSStatus
parseTrustedKeychain		(SSLContextRef		ctx,
							 KCRef				keyChainRef)
{
	CFMutableArrayRef	kcCerts = NULL;		/* all certs in one keychain */
	uint32				numGoodCerts = 0;	/* # of good root certs */
	CSSM_DATA_PTR		certData = NULL;	/* array of CSSM_DATAs */
	CFIndex				certDex;			/* index into kcCerts */
	CFIndex				certsPerKc;			/* # of certs in this KC */
	OSStatus			ortn;
	KCItemRef			kcItem;				/* one cert */
	Boolean				goodCert;
	
	assert(ctx != NULL);
	if(keyChainRef == NULL) {
		return paramErr;
	}
	
	ortn = KCFindX509Certificates(keyChainRef,
		NULL,				// name, XXX
		NULL,				// emailAddress, XXX
		kCertSearchAny,		// options
		&kcCerts);			// results
	switch(ortn) {
		case noErr:
			break;					// proceed
		case errKCItemNotFound:
			return noErr;			// no certs; done
		default:
			sslErrorLog("parseTrustedKeychains: KCFindX509Certificates returned %d\n",
				ortn);
			return ortn;
	}
	if(kcCerts == NULL) {
		sslErrorLog("parseTrustedKeychains: no certs in KC\n");
		return noErr;
	}
	
	/* Note kcCerts must be released on any exit, successful or
	 * otherwise. */
	
	certsPerKc = CFArrayGetCount(kcCerts);	

	/* 
	 * This array gets allocd locally; we'll add it to 
	 * ctx->trustedCerts when we're done.
	 */
	certData = sslMalloc(certsPerKc * sizeof(CSSM_DATA));
	if(certData == NULL) {
		ortn = memFullErr;
		goto errOut;
	}
	memset(certData, 0, certsPerKc * sizeof(CSSM_DATA));
	
	/* 
	 * Build up local certData one root cert at a time. 
	 * Some certs might not pass muster, hence the numGoodCerts
	 * which may or may not increment each time thru.
	 */
	for(certDex=0; certDex<certsPerKc; certDex++) {
		kcItem = (KCItemRef)CFArrayGetValueAtIndex(kcCerts, certDex);
		if(kcItem == NULL) {
			sslErrorLog("parseTrustedKeychains: CF error 1\n");
			ortn = errSSLInternal;
			goto errOut;
		}
		if(!KCIsRootCertificate(kcItem)) {
			/* not root, OK, skip to next cert */
			sslErrorLog("parseTrustedKeychains: cert %d NOT ROOT\n",
					certDex);
			continue;
		}
		ortn = addCertData(ctx,
			kcItem, 
			&certData[numGoodCerts], 
			&goodCert);
		if(ortn) {
			goto errOut;
		}
		if(goodCert) {
			/* added valid root to certData */
			numGoodCerts++;
		}
	}	/* for each cert in kcCerts */

	#if	SSL_DEBUG
	verifyTrustedRoots(ctx, certData, numGoodCerts);
	#endif

	/* Realloc ctx->trustedCerts, add new root certs */
	ctx->trustedCerts = sslRealloc(ctx->trustedCerts, 
		ctx->numTrustedCerts * sizeof(CSSM_DATA),
		(ctx->numTrustedCerts + numGoodCerts) * sizeof(CSSM_DATA));
	if(ctx->trustedCerts == NULL) {
		ortn = memFullErr;
		goto errOut;
	}
	for(certDex=0; certDex<numGoodCerts; certDex++) {
		ctx->trustedCerts[ctx->numTrustedCerts + certDex] = certData[certDex];
	}
	ctx->numTrustedCerts += numGoodCerts;
	ortn = noErr;
	
	#if	SSL_DEBUG
	verifyTrustedRoots(ctx, ctx->trustedCerts, ctx->numTrustedCerts);
	#endif
	
errOut:
	sslFree(certData);
	if(kcCerts != NULL) {
		CFRelease(kcCerts);
	}
	return ortn;
}

/*
 * Given a (supposedly) root cert as a KCItemRef:
 * -- verify that the cert self-verifies
 * -- add its DER-encoded data *certData.
 * -- Add its subjectName to acceptableDNList.
 * -- If all is well, return True in *goodCert.
 *
 * The actual CSSM_DATA.Data is mallocd via CSSM_Malloc. 
 */
static OSStatus
addCertData(
	SSLContext		*ctx,
	KCItemRef		kcItem,
	CSSM_DATA_PTR	certData,
	Boolean			*goodCert)		/* RETURNED */
{	
	UInt32			certSize;
	OSStatus		ortn;
	CSSM_BOOL		subjectExpired;
	
	assert(ctx != NULL);
	assert(certData != NULL);	
	assert(kcItem != NULL);
	assert(goodCert != NULL);
	
	*goodCert = false; 
	
	/* how big is the cert? */
	ortn = KCGetData (kcItem, 0,  NULL, &certSize);
	if(ortn != noErr) {
		sslErrorLog("addCertData: KCGetData(1) returned %d\n", ortn);
		return ortn;
	}

	/* Allocate the buffer. */
	ortn = stSetUpCssmData(certData, certSize);
	if(ortn) {
		return ortn;
	}
	
	/* Get the data. */
	ortn = KCGetData (kcItem, certSize, certData->Data, &certSize);
	if(ortn) {
		sslErrorLog("addCertData: KCGetData(2) returned %d\n", ortn);
		stFreeCssmData(certData, CSSM_FALSE);
		return ortn;
	}

	/* 
	 * Do actual cert verify, which 
     * KCIsRootCertificate does not do. A failure isn't
     * fatal; we just don't add the cert to the array in
     * that case.
     *
     * FIXME - we assume here that our common cspHand can
     * do this cert verify; if not, we have some API work to 
     * do (to let the caller specify which CSP to use with 
     * trusted certs).
     */
	if(!sslVerifyCert(ctx,	
			certData,
			certData,
			ctx->cspHand,
			&subjectExpired)) {			
		sslErrorLog("addCertData: cert does not self-verify!\n");
		stFreeCssmData(certData, CSSM_FALSE);
		return noErr;
	}
	
	/* FIXME - needs update for MANAGES_TRUSTED_ROOTS */
	/* Add this cert's subject name to (poss. existing) acceptableDNList */
	CSSM_DATA_PTR dnData = sslGetCertSubjectName(ctx, certData);
	if(dnData) {
		DNListElem *dn = sslMalloc(sizeof(DNListElem));
		if(dn == NULL) {
			return memFullErr;
		}
		dn->next = ctx->acceptableDNList;
		ctx->acceptableDNList = dn;
		
		/* move actual data to dn; free the CSSM_DATA struct (must be
		 * via CSSM_Free()!) */
		CSSM_TO_SSLBUF(dnData, &dn->derDN);
		sslFree(dnData);
	}
	
	*goodCert = true;
	return noErr;
}

/*
 * Given a newly encountered root cert (obtained from a peer's cert chain),
 * add it to newRootCertKc if the user so allows, and if so, add it to 
 * trustedCerts.
 */
OSStatus
sslAddNewRoot(
	SSLContext			*ctx, 
	const CSSM_DATA_PTR	rootCert)
{
	KCRef			defaultKc;
	Boolean			bDefaultKcExists;
	KCItemRef		certRef = NULL;
	OSStatus		ortn;
	CSSM_DATA_PTR	newTrustee;
	OSStatus			serr;
	
	assert(ctx != NULL);
	assert(rootCert != NULL);
	assert(ctx->newRootCertKc != NULL);	/* caller verifies this */
	
	/*
	 * Get default KC, temporarily set new default.
	 */
	ortn = KCGetDefaultKeychain(&defaultKc);
	if(ortn) {
		bDefaultKcExists = false;
	}
	else {
		bDefaultKcExists = true;
	}
	ortn = KCSetDefaultKeychain(ctx->newRootCertKc);
	if(ortn) {
		sslErrorLog("sslAddNewRoot: KCSetDefaultKeychain returned %d\n", ortn);
		return errSSLUnknownRootCert;
	}

	/*	
	 * Add cert to newRootCertKc. This may well fail due to user
	 * interaction ("Do you want to add this root cert...?").
	 */
	ortn = KCAddX509Certificate(rootCert->Data, rootCert->Length, &certRef);
	
	/* restore default KC in any case */
	if(bDefaultKcExists) {
		KCSetDefaultKeychain(defaultKc);
	}
	if(ortn) {
		sslErrorLog("sslAddNewRoot: KCAddX509Certificate returned %d\n", ortn);
		return errSSLUnknownRootCert;
	}

	/* 
	 * OK, user accepted new root. Now add to our private stash of 
	 * trusted roots. Realloc the whole pile... 
	 */
	ctx->trustedCerts = (CSSM_DATA_PTR)sslRealloc(ctx->trustedCerts,
		(ctx->numTrustedCerts * sizeof(CSSM_DATA)),
		((ctx->numTrustedCerts + 1) * sizeof(CSSM_DATA)));
	if(ctx->trustedCerts == NULL) {
		return memFullErr;
	}
	
	/* Now add a copy of the new root. */
	newTrustee = &ctx->trustedCerts[ctx->numTrustedCerts];
	newTrustee->Data = NULL;
	newTrustee->Length = 0;
	serr = stSetUpCssmData(newTrustee, rootCert->Length);
	if(serr) {
		return serr;
	}
	BlockMove(rootCert->Data, newTrustee->Data, rootCert->Length);
	(ctx->numTrustedCerts)++;
	return noErr;
}

#endif	/* ST_MANAGES_TRUSTED_ROOTS */