TPDatabase.cpp   [plain text]


/*
 * Copyright (c) 2002-2009 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.
 */


/*
 * TPDatabase.cpp - TP's DL/DB access functions.
 *
 * Created 10/9/2002 by Doug Mitchell. 
 */

#include <Security/cssmtype.h>
#include <Security/cssmapi.h> 
#include <security_cdsa_utilities/Schema.h>		/* private API */
#include <security_keychain/TrustKeychains.h>	/* private SecTrustKeychainsGetMutex() */
#include <Security/SecCertificatePriv.h>		/* private SecInferLabelFromX509Name() */
#include <Security/oidscert.h>
#include "TPDatabase.h"
#include "tpdebugging.h"
#include "certGroupUtils.h"
#include "TPCertInfo.h"
#include "TPCrlInfo.h"
#include "tpCrlVerify.h"
#include "tpTime.h"


/*
 * Given a DL/DB, look up cert by subject name. Subsequent
 * certs can be found using the returned result handle.
 */
static CSSM_DB_UNIQUE_RECORD_PTR tpCertLookup(
	CSSM_DL_DB_HANDLE	dlDb,
	const CSSM_DATA		*subjectName,	// DER-encoded
	CSSM_HANDLE_PTR		resultHand,		// RETURNED
	CSSM_DATA_PTR		cert)			// RETURNED
{
	CSSM_QUERY						query;
	CSSM_SELECTION_PREDICATE		predicate;	
	CSSM_DB_UNIQUE_RECORD_PTR		record = NULL;
	
	cert->Data = NULL;
	cert->Length = 0;
	
	/* SWAG until cert schema nailed down */
	predicate.DbOperator = CSSM_DB_EQUAL;
	predicate.Attribute.Info.AttributeNameFormat = 
		CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
	predicate.Attribute.Info.Label.AttributeName = (char*) "Subject";
	predicate.Attribute.Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
	predicate.Attribute.Value = const_cast<CSSM_DATA_PTR>(subjectName);
	predicate.Attribute.NumberOfValues = 1;
	
	query.RecordType = CSSM_DL_DB_RECORD_X509_CERTIFICATE;
	query.Conjunctive = CSSM_DB_NONE;
	query.NumSelectionPredicates = 1;
	query.SelectionPredicate = &predicate;
	query.QueryLimits.TimeLimit = 0;	// FIXME - meaningful?
	query.QueryLimits.SizeLimit = 1;	// FIXME - meaningful?
	query.QueryFlags = 0;				// FIXME - used?
	
	CSSM_DL_DataGetFirst(dlDb,
		&query,
		resultHand,
		NULL,				// don't fetch attributes
		cert,
		&record);

	return record;
}

/*
 * Search a list of DBs for a cert which verifies specified subject item. 
 * Just a boolean return - we found it, or not. If we did, we return
 * TPCertInfo associated with the raw cert. 
 * A true partialIssuerKey on return indicates that caller must deal
 * with partial public key processing later. 
 * If verifyCurrent is true, we will not return a cert which is not
 * temporally valid; else we may well do so. 
 */
TPCertInfo *tpDbFindIssuerCert(
	Allocator				&alloc,
	CSSM_CL_HANDLE			clHand,
	CSSM_CSP_HANDLE			cspHand,
	const TPClItemInfo		*subjectItem,
	const CSSM_DL_DB_LIST	*dbList,
	const char 				*verifyTime,		// may be NULL
	bool					&partialIssuerKey)	// RETURNED
{
	StLock<Mutex> _(SecTrustKeychainsGetMutex());

	uint32						dbDex;
	CSSM_HANDLE					resultHand;
	CSSM_DATA					cert;	
	CSSM_DL_DB_HANDLE			dlDb;
	CSSM_DB_UNIQUE_RECORD_PTR	record;
	TPCertInfo 					*issuerCert = NULL;
	bool 						foundIt;
	TPCertInfo					*expiredIssuer = NULL;
	
	partialIssuerKey = false;
	if(dbList == NULL) {
		return NULL;
	}
	for(dbDex=0; dbDex<dbList->NumHandles; dbDex++) {
		dlDb = dbList->DLDBHandle[dbDex];
		cert.Data = NULL;
		cert.Length = 0;
		resultHand = 0;
		record = tpCertLookup(dlDb,
			subjectItem->issuerName(),
			&resultHand,
			&cert);
		/* remember we have to: 
		 * -- abort this query regardless, and 
		 * -- free the CSSM_DATA cert regardless, and 
		 * -- free the unique record if we don't use it 
		 *    (by placing it in issuerCert)...
		 */
		if(record != NULL) {
			/* Found one */
			assert(cert.Data != NULL);
			tpDbDebug("tpDbFindIssuerCert: found cert record %p", record);
			issuerCert = NULL;
			CSSM_RETURN crtn = CSSM_OK;
			try {
				issuerCert = new TPCertInfo(clHand, cspHand, &cert, TIC_CopyData, verifyTime);
			}
			catch(...) {
				crtn = CSSMERR_TP_INVALID_CERTIFICATE;
			}

			/* we're done with raw cert data */
			tpFreePluginMemory(dlDb.DLHandle, cert.Data);
			cert.Data = NULL;
			cert.Length = 0;
			
			/* Does it verify the subject cert? */
			if(crtn == CSSM_OK) {
				crtn = subjectItem->verifyWithIssuer(issuerCert);
			}
			
			/*
			 * Handle temporal invalidity - if so and this is the first one 
			 * we've seen, hold on to it while we search for better one.
			 */
			if((crtn == CSSM_OK) && (expiredIssuer == NULL)) {
				if(issuerCert->isExpired() || issuerCert->isNotValidYet()) {
					/* 
					 * Exact value not important here, this just uniquely identifies 
					 * this situation in the switch below.
					 */
					tpDbDebug("tpDbFindIssuerCert: holding expired cert (1)");
					crtn = CSSM_CERT_STATUS_EXPIRED;
					expiredIssuer = issuerCert;
					expiredIssuer->dlDbHandle(dlDb);
					expiredIssuer->uniqueRecord(record);
				}
			}
			switch(crtn) {
				case CSSM_OK:
					break;
				case CSSMERR_CSP_APPLE_PUBLIC_KEY_INCOMPLETE:
					partialIssuerKey = true;
					break;
				default:
					issuerCert = NULL;
					if(crtn != CSSM_CERT_STATUS_EXPIRED) {
						delete issuerCert;
						CSSM_DL_FreeUniqueRecord(dlDb, record);
					}
					
					/*
					 * Continue searching this DB. Break on finding the holy 
					 * grail or no more records found. 
					 */
					for(;;) {
						cert.Data = NULL;
						cert.Length = 0;
						record = NULL;
						CSSM_RETURN crtn = CSSM_DL_DataGetNext(dlDb, 
							resultHand,
							NULL,		// no attrs 
							&cert,
							&record);
						if(crtn) {
							/* no more, done with this DB */
							assert(cert.Data == NULL);
							break;
						}
						assert(cert.Data != NULL);
						tpDbDebug("tpDbFindIssuerCert: found cert record %p", record);
						
						/* found one - does it verify subject? */
						try {
							issuerCert = new TPCertInfo(clHand, cspHand, &cert, TIC_CopyData, 
									verifyTime);
						}
						catch(...) {
							crtn = CSSMERR_TP_INVALID_CERTIFICATE;
						}
						/* we're done with raw cert data */
						tpFreePluginMemory(dlDb.DLHandle, cert.Data);
						cert.Data = NULL;
						cert.Length = 0;
	
						if(crtn == CSSM_OK) {
							crtn = subjectItem->verifyWithIssuer(issuerCert);
						}

						/* temporal validity check, again */
						if((crtn == CSSM_OK) && (expiredIssuer == NULL)) {
							if(issuerCert->isExpired() || issuerCert->isNotValidYet()) {
								tpDbDebug("tpDbFindIssuerCert: holding expired cert (2)");
								crtn = CSSM_CERT_STATUS_EXPIRED;
								expiredIssuer = issuerCert;
								expiredIssuer->dlDbHandle(dlDb);
								expiredIssuer->uniqueRecord(record);
							}
						}

						foundIt = false;
						switch(crtn) {
							case CSSM_OK:
								foundIt = true;
								break;
							case CSSMERR_CSP_APPLE_PUBLIC_KEY_INCOMPLETE:
								partialIssuerKey = true;
								foundIt = true;
								break;
							default:
								break;
						}
						if(foundIt) {
							/* yes! */
							break;
						}
						if(crtn != CSSM_CERT_STATUS_EXPIRED) {
							delete issuerCert;
							CSSM_DL_FreeUniqueRecord(dlDb, record);
						}
						issuerCert = NULL;
					} /* searching subsequent records */
			}	/* switch verify */

			if(record != NULL) {
				/* NULL record --> end of search --> DB auto-aborted */
				crtn = CSSM_DL_DataAbortQuery(dlDb, resultHand);
				assert(crtn == CSSM_OK);
			}
			if(issuerCert != NULL) {
				/* successful return */
				tpDbDebug("tpDbFindIssuer: returning record %p", record);
				issuerCert->dlDbHandle(dlDb);
				issuerCert->uniqueRecord(record);
				if(expiredIssuer != NULL) {
					/* We found a replacement */
					tpDbDebug("tpDbFindIssuer: discarding expired cert");
					expiredIssuer->freeUniqueRecord();
					delete expiredIssuer;
				}
				return issuerCert;
			}
		}	/* tpCertLookup, i.e., CSSM_DL_DataGetFirst, succeeded */
		else {
			assert(cert.Data == NULL);
			assert(resultHand == 0);
		}
	}	/* main loop searching dbList */

	if(expiredIssuer != NULL) {
		/* OK, we'll take this one */
		tpDbDebug("tpDbFindIssuer: taking expired cert after all, record %p", 
			expiredIssuer->uniqueRecord());
		return expiredIssuer;
	}
	/* issuer not found */
	return NULL;
}

/*
 * Given a DL/DB, look up CRL by issuer name and validity time. 
 * Subsequent CRLs can be found using the returned result handle. 
 */
#define SEARCH_BY_DATE		1

static CSSM_DB_UNIQUE_RECORD_PTR tpCrlLookup(
	CSSM_DL_DB_HANDLE	dlDb,
	const CSSM_DATA		*issuerName,	// DER-encoded
	CSSM_TIMESTRING 	verifyTime,		// may be NULL, implies "now"
	CSSM_HANDLE_PTR		resultHand,		// RETURNED
	CSSM_DATA_PTR		crl)			// RETURNED
{
	CSSM_QUERY						query;
	CSSM_SELECTION_PREDICATE		pred[3];	
	CSSM_DB_UNIQUE_RECORD_PTR		record = NULL;
	char							timeStr[CSSM_TIME_STRLEN + 1];
	
	crl->Data = NULL;
	crl->Length = 0;
	
	/* Three predicates...first, the issuer name */
	pred[0].DbOperator = CSSM_DB_EQUAL;
	pred[0].Attribute.Info.AttributeNameFormat = 
		CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
	pred[0].Attribute.Info.Label.AttributeName = (char*) "Issuer";
	pred[0].Attribute.Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
	pred[0].Attribute.Value = const_cast<CSSM_DATA_PTR>(issuerName);
	pred[0].Attribute.NumberOfValues = 1;
	
	/* now before/after. Cook up an appropriate time string. */
	if(verifyTime != NULL) {
		/* Caller spec'd tolerate any format */
		int rtn = tpTimeToCssmTimestring(verifyTime, strlen(verifyTime), timeStr);
		if(rtn) {
			tpErrorLog("tpCrlLookup: Invalid VerifyTime string\n");
			return NULL;
		}
	}
	else {
		/* right now */
		StLock<Mutex> _(tpTimeLock());
		timeAtNowPlus(0, TIME_CSSM, timeStr);
	}
	CSSM_DATA timeData;
	timeData.Data = (uint8 *)timeStr;
	timeData.Length = CSSM_TIME_STRLEN;
	
	#if SEARCH_BY_DATE
	pred[1].DbOperator = CSSM_DB_LESS_THAN;
	pred[1].Attribute.Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
	pred[1].Attribute.Info.Label.AttributeName = (char*) "NextUpdate";
	pred[1].Attribute.Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
	pred[1].Attribute.Value = &timeData;
	pred[1].Attribute.NumberOfValues = 1;
	
	pred[2].DbOperator = CSSM_DB_GREATER_THAN;
	pred[2].Attribute.Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
	pred[2].Attribute.Info.Label.AttributeName = (char*) "ThisUpdate";
	pred[2].Attribute.Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
	pred[2].Attribute.Value = &timeData;
	pred[2].Attribute.NumberOfValues = 1;
	#endif
	
	query.RecordType = CSSM_DL_DB_RECORD_X509_CRL;
	query.Conjunctive = CSSM_DB_AND;
	#if SEARCH_BY_DATE
	query.NumSelectionPredicates = 3;
	#else
	query.NumSelectionPredicates = 1;
	#endif
	query.SelectionPredicate = pred;
	query.QueryLimits.TimeLimit = 0;	// FIXME - meaningful?
	query.QueryLimits.SizeLimit = 1;	// FIXME - meaningful?
	query.QueryFlags = 0;				// FIXME - used?
	
	CSSM_DL_DataGetFirst(dlDb,
		&query,
		resultHand,
		NULL,				// don't fetch attributes
		crl,
		&record);
	return record;
}

/*
 * Search a list of DBs for a CRL from the specified issuer and (optional)  
 * TPVerifyContext.verifyTime. 
 * Just a boolean return - we found it, or not. If we did, we return a
 * TPCrlInfo which has been verified with the specified TPVerifyContext.
 */
TPCrlInfo *tpDbFindIssuerCrl(
	TPVerifyContext		&vfyCtx,
	const CSSM_DATA		&issuer,
	TPCertInfo			&forCert)
{
	StLock<Mutex> _(SecTrustKeychainsGetMutex());

	uint32						dbDex;
	CSSM_HANDLE					resultHand;
	CSSM_DATA					crl;	
	CSSM_DL_DB_HANDLE			dlDb;
	CSSM_DB_UNIQUE_RECORD_PTR	record;
	TPCrlInfo 					*issuerCrl = NULL;
	CSSM_DL_DB_LIST_PTR 		dbList = vfyCtx.dbList;
	CSSM_RETURN					crtn;
	
	if(dbList == NULL) {
		return NULL;
	}
	for(dbDex=0; dbDex<dbList->NumHandles; dbDex++) {
		dlDb = dbList->DLDBHandle[dbDex];
		crl.Data = NULL;
		crl.Length = 0;
		record = tpCrlLookup(dlDb,
			&issuer,
			vfyCtx.verifyTime,
			&resultHand,
			&crl);
		/* remember we have to: 
		 * -- abort this query regardless, and 
		 * -- free the CSSM_DATA crl regardless, and 
		 * -- free the unique record if we don't use it 
		 *    (by placing it in issuerCert)...
		 */
		if(record != NULL) {
			/* Found one */
			assert(crl.Data != NULL);
			issuerCrl = new TPCrlInfo(vfyCtx.clHand, 
				vfyCtx.cspHand,
				&crl, 
				TIC_CopyData, 
				vfyCtx.verifyTime);
			/* we're done with raw CRL data */
			/* FIXME this assumes that vfyCtx.alloc is the same as the 
			 * allocator associated with DlDB...OK? */
			tpFreeCssmData(vfyCtx.alloc, &crl, CSSM_FALSE);
			crl.Data = NULL;
			crl.Length = 0;
			
			/* and we're done with the record */
			CSSM_DL_FreeUniqueRecord(dlDb, record);
			
			/* Does it verify with specified context? */
			crtn = issuerCrl->verifyWithContextNow(vfyCtx, &forCert);
			if(crtn) {
					
				delete issuerCrl;
				issuerCrl = NULL;
				
				/*
				 * Verify fail. Continue searching this DB. Break on 
				 * finding the holy grail or no more records found. 
				 */
				for(;;) {
					crl.Data = NULL;
					crl.Length = 0;
					crtn = CSSM_DL_DataGetNext(dlDb, 
						resultHand,
						NULL,		// no attrs 
						&crl,
						&record);
					if(crtn) {
						/* no more, done with this DB */
						assert(crl.Data == NULL);
						break;
					}
					assert(crl.Data != NULL);
					
					/* found one - is it any good? */
					issuerCrl = new TPCrlInfo(vfyCtx.clHand, 
						vfyCtx.cspHand,
						&crl, 
						TIC_CopyData, 
						vfyCtx.verifyTime);
					/* we're done with raw CRL data */
					/* FIXME this assumes that vfyCtx.alloc is the same as the 
					* allocator associated with DlDB...OK? */
					tpFreeCssmData(vfyCtx.alloc, &crl, CSSM_FALSE);
					crl.Data = NULL;
					crl.Length = 0;

					CSSM_DL_FreeUniqueRecord(dlDb, record);

					crtn = issuerCrl->verifyWithContextNow(vfyCtx, &forCert);
					if(crtn == CSSM_OK) {
						/* yes! */
						break;
					}
					delete issuerCrl;
					issuerCrl = NULL;
				} /* searching subsequent records */
			}	/* verify fail */
			/* else success! */

			if(issuerCrl != NULL) {
				/* successful return */
				CSSM_DL_DataAbortQuery(dlDb, resultHand);
				tpDebug("tpDbFindIssuerCrl: found CRL record %p", record);
				return issuerCrl;
			}
		}	/* tpCrlLookup, i.e., CSSM_DL_DataGetFirst, succeeded */
		else {
			assert(crl.Data == NULL);
		}
		/* in any case, abort the query for this db */
		CSSM_DL_DataAbortQuery(dlDb, resultHand);
		
	}	/* main loop searching dbList */

	/* issuer not found */
	return NULL;
}