1/*
2 * Copyright (c) 2002-2009 Apple Inc. All Rights Reserved.
3 *
4 * The contents of this file constitute Original Code as defined in and are
5 * subject to the Apple Public Source License Version 1.2 (the 'License').
6 * You may not use this file except in compliance with the License. Please obtain
7 * a copy of the License at http://www.apple.com/publicsource and read it before
8 * using this file.
9 *
10 * This Original Code and all software distributed under the License are
11 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS
12 * OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT
13 * LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14 * PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the
15 * specific language governing rights and limitations under the License.
16 */
17
18
19/*
20 * TPDatabase.cpp - TP's DL/DB access functions.
21 *
22 * Created 10/9/2002 by Doug Mitchell.
23 */
24
25#include <Security/cssmtype.h>
26#include <Security/cssmapi.h>
27#include <security_cdsa_utilities/Schema.h>		/* private API */
28#include <security_keychain/TrustKeychains.h>	/* private SecTrustKeychainsGetMutex() */
29#include <Security/SecCertificatePriv.h>		/* private SecInferLabelFromX509Name() */
30#include <Security/oidscert.h>
31#include "TPDatabase.h"
32#include "tpdebugging.h"
33#include "certGroupUtils.h"
34#include "TPCertInfo.h"
35#include "TPCrlInfo.h"
36#include "tpCrlVerify.h"
37#include "tpTime.h"
38
39
40/*
41 * Given a DL/DB, look up cert by subject name. Subsequent
42 * certs can be found using the returned result handle.
43 */
44static CSSM_DB_UNIQUE_RECORD_PTR tpCertLookup(
45	CSSM_DL_DB_HANDLE	dlDb,
46	const CSSM_DATA		*subjectName,	// DER-encoded
47	CSSM_HANDLE_PTR		resultHand,		// RETURNED
48	CSSM_DATA_PTR		cert)			// RETURNED
49{
50	CSSM_QUERY						query;
51	CSSM_SELECTION_PREDICATE		predicate;
52	CSSM_DB_UNIQUE_RECORD_PTR		record = NULL;
53
54	cert->Data = NULL;
55	cert->Length = 0;
56
57	/* SWAG until cert schema nailed down */
58	predicate.DbOperator = CSSM_DB_EQUAL;
59	predicate.Attribute.Info.AttributeNameFormat =
60		CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
61	predicate.Attribute.Info.Label.AttributeName = (char*) "Subject";
62	predicate.Attribute.Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
63	predicate.Attribute.Value = const_cast<CSSM_DATA_PTR>(subjectName);
64	predicate.Attribute.NumberOfValues = 1;
65
66	query.RecordType = CSSM_DL_DB_RECORD_X509_CERTIFICATE;
67	query.Conjunctive = CSSM_DB_NONE;
68	query.NumSelectionPredicates = 1;
69	query.SelectionPredicate = &predicate;
70	query.QueryLimits.TimeLimit = 0;	// FIXME - meaningful?
71	query.QueryLimits.SizeLimit = 1;	// FIXME - meaningful?
72	query.QueryFlags = 0;				// FIXME - used?
73
74	CSSM_DL_DataGetFirst(dlDb,
75		&query,
76		resultHand,
77		NULL,				// don't fetch attributes
78		cert,
79		&record);
80
81	return record;
82}
83
84/*
85 * Search a list of DBs for a cert which verifies specified subject item.
86 * Just a boolean return - we found it, or not. If we did, we return
87 * TPCertInfo associated with the raw cert.
88 * A true partialIssuerKey on return indicates that caller must deal
89 * with partial public key processing later.
90 * If verifyCurrent is true, we will not return a cert which is not
91 * temporally valid; else we may well do so.
92 */
93TPCertInfo *tpDbFindIssuerCert(
94	Allocator				&alloc,
95	CSSM_CL_HANDLE			clHand,
96	CSSM_CSP_HANDLE			cspHand,
97	const TPClItemInfo		*subjectItem,
98	const CSSM_DL_DB_LIST	*dbList,
99	const char 				*verifyTime,		// may be NULL
100	bool					&partialIssuerKey)	// RETURNED
101{
102	StLock<Mutex> _(SecTrustKeychainsGetMutex());
103
104	uint32						dbDex;
105	CSSM_HANDLE					resultHand;
106	CSSM_DATA					cert;
107	CSSM_DL_DB_HANDLE			dlDb;
108	CSSM_DB_UNIQUE_RECORD_PTR	record;
109	TPCertInfo 					*issuerCert = NULL;
110	bool 						foundIt;
111	TPCertInfo					*expiredIssuer = NULL;
112
113	partialIssuerKey = false;
114	if(dbList == NULL) {
115		return NULL;
116	}
117	for(dbDex=0; dbDex<dbList->NumHandles; dbDex++) {
118		dlDb = dbList->DLDBHandle[dbDex];
119		cert.Data = NULL;
120		cert.Length = 0;
121		resultHand = 0;
122		record = tpCertLookup(dlDb,
123			subjectItem->issuerName(),
124			&resultHand,
125			&cert);
126		/* remember we have to:
127		 * -- abort this query regardless, and
128		 * -- free the CSSM_DATA cert regardless, and
129		 * -- free the unique record if we don't use it
130		 *    (by placing it in issuerCert)...
131		 */
132		if(record != NULL) {
133			/* Found one */
134			assert(cert.Data != NULL);
135			tpDbDebug("tpDbFindIssuerCert: found cert record %p", record);
136			issuerCert = NULL;
137			CSSM_RETURN crtn = CSSM_OK;
138			try {
139				issuerCert = new TPCertInfo(clHand, cspHand, &cert, TIC_CopyData, verifyTime);
140			}
141			catch(...) {
142				crtn = CSSMERR_TP_INVALID_CERTIFICATE;
143			}
144
145			/* we're done with raw cert data */
146			tpFreePluginMemory(dlDb.DLHandle, cert.Data);
147			cert.Data = NULL;
148			cert.Length = 0;
149
150			/* Does it verify the subject cert? */
151			if(crtn == CSSM_OK) {
152				crtn = subjectItem->verifyWithIssuer(issuerCert);
153			}
154
155			/*
156			 * Handle temporal invalidity - if so and this is the first one
157			 * we've seen, hold on to it while we search for better one.
158			 */
159			if((crtn == CSSM_OK) && (expiredIssuer == NULL)) {
160				if(issuerCert->isExpired() || issuerCert->isNotValidYet()) {
161					/*
162					 * Exact value not important here, this just uniquely identifies
163					 * this situation in the switch below.
164					 */
165					tpDbDebug("tpDbFindIssuerCert: holding expired cert (1)");
166					crtn = CSSM_CERT_STATUS_EXPIRED;
167					expiredIssuer = issuerCert;
168					expiredIssuer->dlDbHandle(dlDb);
169					expiredIssuer->uniqueRecord(record);
170				}
171			}
172			switch(crtn) {
173				case CSSM_OK:
174					break;
175				case CSSMERR_CSP_APPLE_PUBLIC_KEY_INCOMPLETE:
176					partialIssuerKey = true;
177					break;
178				default:
179					issuerCert = NULL;
180					if(crtn != CSSM_CERT_STATUS_EXPIRED) {
181						delete issuerCert;
182						CSSM_DL_FreeUniqueRecord(dlDb, record);
183					}
184
185					/*
186					 * Continue searching this DB. Break on finding the holy
187					 * grail or no more records found.
188					 */
189					for(;;) {
190						cert.Data = NULL;
191						cert.Length = 0;
192						record = NULL;
193						CSSM_RETURN crtn = CSSM_DL_DataGetNext(dlDb,
194							resultHand,
195							NULL,		// no attrs
196							&cert,
197							&record);
198						if(crtn) {
199							/* no more, done with this DB */
200							assert(cert.Data == NULL);
201							break;
202						}
203						assert(cert.Data != NULL);
204						tpDbDebug("tpDbFindIssuerCert: found cert record %p", record);
205
206						/* found one - does it verify subject? */
207						try {
208							issuerCert = new TPCertInfo(clHand, cspHand, &cert, TIC_CopyData,
209									verifyTime);
210						}
211						catch(...) {
212							crtn = CSSMERR_TP_INVALID_CERTIFICATE;
213						}
214						/* we're done with raw cert data */
215						tpFreePluginMemory(dlDb.DLHandle, cert.Data);
216						cert.Data = NULL;
217						cert.Length = 0;
218
219						if(crtn == CSSM_OK) {
220							crtn = subjectItem->verifyWithIssuer(issuerCert);
221						}
222
223						/* temporal validity check, again */
224						if((crtn == CSSM_OK) && (expiredIssuer == NULL)) {
225							if(issuerCert->isExpired() || issuerCert->isNotValidYet()) {
226								tpDbDebug("tpDbFindIssuerCert: holding expired cert (2)");
227								crtn = CSSM_CERT_STATUS_EXPIRED;
228								expiredIssuer = issuerCert;
229								expiredIssuer->dlDbHandle(dlDb);
230								expiredIssuer->uniqueRecord(record);
231							}
232						}
233
234						foundIt = false;
235						switch(crtn) {
236							case CSSM_OK:
237								foundIt = true;
238								break;
239							case CSSMERR_CSP_APPLE_PUBLIC_KEY_INCOMPLETE:
240								partialIssuerKey = true;
241								foundIt = true;
242								break;
243							default:
244								break;
245						}
246						if(foundIt) {
247							/* yes! */
248							break;
249						}
250						if(crtn != CSSM_CERT_STATUS_EXPIRED) {
251							delete issuerCert;
252							CSSM_DL_FreeUniqueRecord(dlDb, record);
253						}
254						issuerCert = NULL;
255					} /* searching subsequent records */
256			}	/* switch verify */
257
258			if(record != NULL) {
259				/* NULL record --> end of search --> DB auto-aborted */
260				crtn = CSSM_DL_DataAbortQuery(dlDb, resultHand);
261				assert(crtn == CSSM_OK);
262			}
263			if(issuerCert != NULL) {
264				/* successful return */
265				tpDbDebug("tpDbFindIssuer: returning record %p", record);
266				issuerCert->dlDbHandle(dlDb);
267				issuerCert->uniqueRecord(record);
268				if(expiredIssuer != NULL) {
269					/* We found a replacement */
270					tpDbDebug("tpDbFindIssuer: discarding expired cert");
271					expiredIssuer->freeUniqueRecord();
272					delete expiredIssuer;
273				}
274				return issuerCert;
275			}
276		}	/* tpCertLookup, i.e., CSSM_DL_DataGetFirst, succeeded */
277		else {
278			assert(cert.Data == NULL);
279			assert(resultHand == 0);
280		}
281	}	/* main loop searching dbList */
282
283	if(expiredIssuer != NULL) {
284		/* OK, we'll take this one */
285		tpDbDebug("tpDbFindIssuer: taking expired cert after all, record %p",
286			expiredIssuer->uniqueRecord());
287		return expiredIssuer;
288	}
289	/* issuer not found */
290	return NULL;
291}
292
293/*
294 * Given a DL/DB, look up CRL by issuer name and validity time.
295 * Subsequent CRLs can be found using the returned result handle.
296 */
297#define SEARCH_BY_DATE		1
298
299static CSSM_DB_UNIQUE_RECORD_PTR tpCrlLookup(
300	CSSM_DL_DB_HANDLE	dlDb,
301	const CSSM_DATA		*issuerName,	// DER-encoded
302	CSSM_TIMESTRING 	verifyTime,		// may be NULL, implies "now"
303	CSSM_HANDLE_PTR		resultHand,		// RETURNED
304	CSSM_DATA_PTR		crl)			// RETURNED
305{
306	CSSM_QUERY						query;
307	CSSM_SELECTION_PREDICATE		pred[3];
308	CSSM_DB_UNIQUE_RECORD_PTR		record = NULL;
309	char							timeStr[CSSM_TIME_STRLEN + 1];
310
311	crl->Data = NULL;
312	crl->Length = 0;
313
314	/* Three predicates...first, the issuer name */
315	pred[0].DbOperator = CSSM_DB_EQUAL;
316	pred[0].Attribute.Info.AttributeNameFormat =
317		CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
318	pred[0].Attribute.Info.Label.AttributeName = (char*) "Issuer";
319	pred[0].Attribute.Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
320	pred[0].Attribute.Value = const_cast<CSSM_DATA_PTR>(issuerName);
321	pred[0].Attribute.NumberOfValues = 1;
322
323	/* now before/after. Cook up an appropriate time string. */
324	if(verifyTime != NULL) {
325		/* Caller spec'd tolerate any format */
326		int rtn = tpTimeToCssmTimestring(verifyTime, (unsigned)strlen(verifyTime), timeStr);
327		if(rtn) {
328			tpErrorLog("tpCrlLookup: Invalid VerifyTime string\n");
329			return NULL;
330		}
331	}
332	else {
333		/* right now */
334		StLock<Mutex> _(tpTimeLock());
335		timeAtNowPlus(0, TIME_CSSM, timeStr);
336	}
337	CSSM_DATA timeData;
338	timeData.Data = (uint8 *)timeStr;
339	timeData.Length = CSSM_TIME_STRLEN;
340
341	#if SEARCH_BY_DATE
342	pred[1].DbOperator = CSSM_DB_LESS_THAN;
343	pred[1].Attribute.Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
344	pred[1].Attribute.Info.Label.AttributeName = (char*) "NextUpdate";
345	pred[1].Attribute.Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
346	pred[1].Attribute.Value = &timeData;
347	pred[1].Attribute.NumberOfValues = 1;
348
349	pred[2].DbOperator = CSSM_DB_GREATER_THAN;
350	pred[2].Attribute.Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
351	pred[2].Attribute.Info.Label.AttributeName = (char*) "ThisUpdate";
352	pred[2].Attribute.Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
353	pred[2].Attribute.Value = &timeData;
354	pred[2].Attribute.NumberOfValues = 1;
355	#endif
356
357	query.RecordType = CSSM_DL_DB_RECORD_X509_CRL;
358	query.Conjunctive = CSSM_DB_AND;
359	#if SEARCH_BY_DATE
360	query.NumSelectionPredicates = 3;
361	#else
362	query.NumSelectionPredicates = 1;
363	#endif
364	query.SelectionPredicate = pred;
365	query.QueryLimits.TimeLimit = 0;	// FIXME - meaningful?
366	query.QueryLimits.SizeLimit = 1;	// FIXME - meaningful?
367	query.QueryFlags = 0;				// FIXME - used?
368
369	CSSM_DL_DataGetFirst(dlDb,
370		&query,
371		resultHand,
372		NULL,				// don't fetch attributes
373		crl,
374		&record);
375	return record;
376}
377
378/*
379 * Search a list of DBs for a CRL from the specified issuer and (optional)
380 * TPVerifyContext.verifyTime.
381 * Just a boolean return - we found it, or not. If we did, we return a
382 * TPCrlInfo which has been verified with the specified TPVerifyContext.
383 */
384TPCrlInfo *tpDbFindIssuerCrl(
385	TPVerifyContext		&vfyCtx,
386	const CSSM_DATA		&issuer,
387	TPCertInfo			&forCert)
388{
389	StLock<Mutex> _(SecTrustKeychainsGetMutex());
390
391	uint32						dbDex;
392	CSSM_HANDLE					resultHand;
393	CSSM_DATA					crl;
394	CSSM_DL_DB_HANDLE			dlDb;
395	CSSM_DB_UNIQUE_RECORD_PTR	record;
396	TPCrlInfo 					*issuerCrl = NULL;
397	CSSM_DL_DB_LIST_PTR 		dbList = vfyCtx.dbList;
398	CSSM_RETURN					crtn;
399
400	if(dbList == NULL) {
401		return NULL;
402	}
403	for(dbDex=0; dbDex<dbList->NumHandles; dbDex++) {
404		dlDb = dbList->DLDBHandle[dbDex];
405		crl.Data = NULL;
406		crl.Length = 0;
407		record = tpCrlLookup(dlDb,
408			&issuer,
409			vfyCtx.verifyTime,
410			&resultHand,
411			&crl);
412		/* remember we have to:
413		 * -- abort this query regardless, and
414		 * -- free the CSSM_DATA crl regardless, and
415		 * -- free the unique record if we don't use it
416		 *    (by placing it in issuerCert)...
417		 */
418		if(record != NULL) {
419			/* Found one */
420			assert(crl.Data != NULL);
421			issuerCrl = new TPCrlInfo(vfyCtx.clHand,
422				vfyCtx.cspHand,
423				&crl,
424				TIC_CopyData,
425				vfyCtx.verifyTime);
426			/* we're done with raw CRL data */
427			/* FIXME this assumes that vfyCtx.alloc is the same as the
428			 * allocator associated with DlDB...OK? */
429			tpFreeCssmData(vfyCtx.alloc, &crl, CSSM_FALSE);
430			crl.Data = NULL;
431			crl.Length = 0;
432
433			/* and we're done with the record */
434			CSSM_DL_FreeUniqueRecord(dlDb, record);
435
436			/* Does it verify with specified context? */
437			crtn = issuerCrl->verifyWithContextNow(vfyCtx, &forCert);
438			if(crtn) {
439
440				delete issuerCrl;
441				issuerCrl = NULL;
442
443				/*
444				 * Verify fail. Continue searching this DB. Break on
445				 * finding the holy grail or no more records found.
446				 */
447				for(;;) {
448					crl.Data = NULL;
449					crl.Length = 0;
450					crtn = CSSM_DL_DataGetNext(dlDb,
451						resultHand,
452						NULL,		// no attrs
453						&crl,
454						&record);
455					if(crtn) {
456						/* no more, done with this DB */
457						assert(crl.Data == NULL);
458						break;
459					}
460					assert(crl.Data != NULL);
461
462					/* found one - is it any good? */
463					issuerCrl = new TPCrlInfo(vfyCtx.clHand,
464						vfyCtx.cspHand,
465						&crl,
466						TIC_CopyData,
467						vfyCtx.verifyTime);
468					/* we're done with raw CRL data */
469					/* FIXME this assumes that vfyCtx.alloc is the same as the
470					* allocator associated with DlDB...OK? */
471					tpFreeCssmData(vfyCtx.alloc, &crl, CSSM_FALSE);
472					crl.Data = NULL;
473					crl.Length = 0;
474
475					CSSM_DL_FreeUniqueRecord(dlDb, record);
476
477					crtn = issuerCrl->verifyWithContextNow(vfyCtx, &forCert);
478					if(crtn == CSSM_OK) {
479						/* yes! */
480						break;
481					}
482					delete issuerCrl;
483					issuerCrl = NULL;
484				} /* searching subsequent records */
485			}	/* verify fail */
486			/* else success! */
487
488			if(issuerCrl != NULL) {
489				/* successful return */
490				CSSM_DL_DataAbortQuery(dlDb, resultHand);
491				tpDebug("tpDbFindIssuerCrl: found CRL record %p", record);
492				return issuerCrl;
493			}
494		}	/* tpCrlLookup, i.e., CSSM_DL_DataGetFirst, succeeded */
495		else {
496			assert(crl.Data == NULL);
497		}
498		/* in any case, abort the query for this db */
499		CSSM_DL_DataAbortQuery(dlDb, resultHand);
500
501	}	/* main loop searching dbList */
502
503	/* issuer not found */
504	return NULL;
505}
506
507