1/*
2 * Copyright (c) 2002-2009,2011-2012,2014 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 */
23
24#include <Security/cssmtype.h>
25#include <Security/cssmapi.h>
26#include <security_cdsa_utilities/Schema.h>		/* private API */
27#include <security_keychain/TrustKeychains.h>	/* private SecTrustKeychainsGetMutex() */
28#include <Security/SecCertificatePriv.h>		/* private SecInferLabelFromX509Name() */
29#include <Security/oidscert.h>
30#include "TPDatabase.h"
31#include "tpdebugging.h"
32#include "certGroupUtils.h"
33#include "TPCertInfo.h"
34#include "TPCrlInfo.h"
35#include "tpCrlVerify.h"
36#include "tpTime.h"
37
38
39/*
40 * Given a DL/DB, look up cert by subject name. Subsequent
41 * certs can be found using the returned result handle.
42 */
43static CSSM_DB_UNIQUE_RECORD_PTR tpCertLookup(
44	CSSM_DL_DB_HANDLE	dlDb,
45	const CSSM_DATA		*subjectName,	// DER-encoded
46	CSSM_HANDLE_PTR		resultHand,		// RETURNED
47	CSSM_DATA_PTR		cert)			// RETURNED
48{
49	CSSM_QUERY						query;
50	CSSM_SELECTION_PREDICATE		predicate;
51	CSSM_DB_UNIQUE_RECORD_PTR		record = NULL;
52
53	cert->Data = NULL;
54	cert->Length = 0;
55
56	/* SWAG until cert schema nailed down */
57	predicate.DbOperator = CSSM_DB_EQUAL;
58	predicate.Attribute.Info.AttributeNameFormat =
59		CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
60	predicate.Attribute.Info.Label.AttributeName = (char*) "Subject";
61	predicate.Attribute.Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
62	predicate.Attribute.Value = const_cast<CSSM_DATA_PTR>(subjectName);
63	predicate.Attribute.NumberOfValues = 1;
64
65	query.RecordType = CSSM_DL_DB_RECORD_X509_CERTIFICATE;
66	query.Conjunctive = CSSM_DB_NONE;
67	query.NumSelectionPredicates = 1;
68	query.SelectionPredicate = &predicate;
69	query.QueryLimits.TimeLimit = 0;	// FIXME - meaningful?
70	query.QueryLimits.SizeLimit = 1;	// FIXME - meaningful?
71	query.QueryFlags = 0;				// FIXME - used?
72
73	CSSM_DL_DataGetFirst(dlDb,
74		&query,
75		resultHand,
76		NULL,				// don't fetch attributes
77		cert,
78		&record);
79
80	return record;
81}
82
83/*
84 * Search a list of DBs for a cert which verifies specified subject item.
85 * Just a boolean return - we found it, or not. If we did, we return
86 * TPCertInfo associated with the raw cert.
87 * A true partialIssuerKey on return indicates that caller must deal
88 * with partial public key processing later.
89 * If verifyCurrent is true, we will not return a cert which is not
90 * temporally valid; else we may well do so.
91 */
92TPCertInfo *tpDbFindIssuerCert(
93	Allocator				&alloc,
94	CSSM_CL_HANDLE			clHand,
95	CSSM_CSP_HANDLE			cspHand,
96	const TPClItemInfo		*subjectItem,
97	const CSSM_DL_DB_LIST	*dbList,
98	const char 				*verifyTime,		// may be NULL
99	bool					&partialIssuerKey)	// RETURNED
100{
101	StLock<Mutex> _(SecTrustKeychainsGetMutex());
102
103	uint32						dbDex;
104	CSSM_HANDLE					resultHand;
105	CSSM_DATA					cert;
106	CSSM_DL_DB_HANDLE			dlDb;
107	CSSM_DB_UNIQUE_RECORD_PTR	record;
108	TPCertInfo 					*issuerCert = NULL;
109	bool 						foundIt;
110	TPCertInfo					*expiredIssuer = NULL;
111	TPCertInfo					*nonRootIssuer = 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 (1) %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			/*
173			 * Prefer a root over an intermediate issuer if we can get one
174			 * (in case a cross-signed intermediate and root are both available)
175			 */
176			if((crtn == CSSM_OK) && (nonRootIssuer == NULL)) {
177				if(!issuerCert->isSelfSigned()) {
178					/*
179					 * Exact value not important here, this just uniquely identifies
180					 * this situation in the switch below.
181					 */
182					tpDbDebug("tpDbFindIssuerCert: holding non-root cert (1)");
183					crtn = CSSM_CERT_STATUS_IS_ROOT;
184					nonRootIssuer = issuerCert;
185					nonRootIssuer->dlDbHandle(dlDb);
186					nonRootIssuer->uniqueRecord(record);
187				}
188			}
189			switch(crtn) {
190				case CSSM_OK:
191					break;
192				case CSSMERR_CSP_APPLE_PUBLIC_KEY_INCOMPLETE:
193					partialIssuerKey = true;
194					break;
195				default:
196					if(issuerCert != NULL) {
197						/* either holding onto this cert, or done with it. */
198						if(crtn != CSSM_CERT_STATUS_EXPIRED &&
199						   crtn != CSSM_CERT_STATUS_IS_ROOT) {
200							delete issuerCert;
201							CSSM_DL_FreeUniqueRecord(dlDb, record);
202						}
203						issuerCert = NULL;
204					}
205
206					/*
207					 * Continue searching this DB. Break on finding the holy
208					 * grail or no more records found.
209					 */
210					for(;;) {
211						cert.Data = NULL;
212						cert.Length = 0;
213						record = NULL;
214						CSSM_RETURN crtn = CSSM_DL_DataGetNext(dlDb,
215							resultHand,
216							NULL,		// no attrs
217							&cert,
218							&record);
219						if(crtn) {
220							/* no more, done with this DB */
221							assert(cert.Data == NULL);
222							break;
223						}
224						assert(cert.Data != NULL);
225						tpDbDebug("tpDbFindIssuerCert: found cert record (2) %p", record);
226
227						/* found one - does it verify subject? */
228						try {
229							issuerCert = new TPCertInfo(clHand, cspHand, &cert, TIC_CopyData,
230									verifyTime);
231						}
232						catch(...) {
233							crtn = CSSMERR_TP_INVALID_CERTIFICATE;
234						}
235						/* we're done with raw cert data */
236						tpFreePluginMemory(dlDb.DLHandle, cert.Data);
237						cert.Data = NULL;
238						cert.Length = 0;
239
240						if(crtn == CSSM_OK) {
241							crtn = subjectItem->verifyWithIssuer(issuerCert);
242						}
243
244						/* temporal validity check, again */
245						if((crtn == CSSM_OK) && (expiredIssuer == NULL)) {
246							if(issuerCert->isExpired() || issuerCert->isNotValidYet()) {
247								tpDbDebug("tpDbFindIssuerCert: holding expired cert (2)");
248								crtn = CSSM_CERT_STATUS_EXPIRED;
249								expiredIssuer = issuerCert;
250								expiredIssuer->dlDbHandle(dlDb);
251								expiredIssuer->uniqueRecord(record);
252							}
253						}
254						/* self-signed check, again */
255						if((crtn == CSSM_OK) && (nonRootIssuer == NULL)) {
256							if(!issuerCert->isSelfSigned()) {
257								tpDbDebug("tpDbFindIssuerCert: holding non-root cert (2)");
258								crtn = CSSM_CERT_STATUS_IS_ROOT;
259								nonRootIssuer = issuerCert;
260								nonRootIssuer->dlDbHandle(dlDb);
261								nonRootIssuer->uniqueRecord(record);
262							}
263						}
264
265						foundIt = false;
266						switch(crtn) {
267							case CSSM_OK:
268								foundIt = true;
269								break;
270							case CSSMERR_CSP_APPLE_PUBLIC_KEY_INCOMPLETE:
271								partialIssuerKey = true;
272								foundIt = true;
273								break;
274							default:
275								break;
276						}
277						if(foundIt) {
278							/* yes! */
279							break;
280						}
281						if(issuerCert != NULL) {
282							/* either holding onto this cert, or done with it. */
283							if(crtn != CSSM_CERT_STATUS_EXPIRED &&
284							   crtn != CSSM_CERT_STATUS_IS_ROOT) {
285								delete issuerCert;
286								CSSM_DL_FreeUniqueRecord(dlDb, record);
287							}
288							issuerCert = NULL;
289						}
290					} /* searching subsequent records */
291			}	/* switch verify */
292
293			if(record != NULL) {
294				/* NULL record --> end of search --> DB auto-aborted */
295				crtn = CSSM_DL_DataAbortQuery(dlDb, resultHand);
296				assert(crtn == CSSM_OK);
297			}
298			if(issuerCert != NULL) {
299				/* successful return */
300				tpDbDebug("tpDbFindIssuer: returning record %p", record);
301				issuerCert->dlDbHandle(dlDb);
302				issuerCert->uniqueRecord(record);
303				if(expiredIssuer != NULL) {
304					/* We found a replacement */
305					tpDbDebug("tpDbFindIssuer: discarding expired cert");
306					expiredIssuer->freeUniqueRecord();
307					delete expiredIssuer;
308				}
309				/* Avoid deleting the non-root cert if same as expired cert */
310				if(nonRootIssuer != NULL && nonRootIssuer != expiredIssuer) {
311					/* We found a replacement */
312					tpDbDebug("tpDbFindIssuer: discarding non-root cert");
313					nonRootIssuer->freeUniqueRecord();
314					delete nonRootIssuer;
315				}
316				return issuerCert;
317			}
318		}	/* tpCertLookup, i.e., CSSM_DL_DataGetFirst, succeeded */
319		else {
320			assert(cert.Data == NULL);
321			assert(resultHand == 0);
322		}
323	}	/* main loop searching dbList */
324
325	if(nonRootIssuer != NULL) {
326		/* didn't find root issuer, so use this one */
327		tpDbDebug("tpDbFindIssuer: taking non-root issuer cert, record %p",
328			nonRootIssuer->uniqueRecord());
329		if(expiredIssuer != NULL && expiredIssuer != nonRootIssuer) {
330			expiredIssuer->freeUniqueRecord();
331			delete expiredIssuer;
332		}
333		return nonRootIssuer;
334	}
335
336	if(expiredIssuer != NULL) {
337		/* OK, we'll take this one */
338		tpDbDebug("tpDbFindIssuer: taking expired cert after all, record %p",
339			expiredIssuer->uniqueRecord());
340		return expiredIssuer;
341	}
342	/* issuer not found */
343	return NULL;
344}
345
346/*
347 * Given a DL/DB, look up CRL by issuer name and validity time.
348 * Subsequent CRLs can be found using the returned result handle.
349 */
350#define SEARCH_BY_DATE		1
351
352static CSSM_DB_UNIQUE_RECORD_PTR tpCrlLookup(
353	CSSM_DL_DB_HANDLE	dlDb,
354	const CSSM_DATA		*issuerName,	// DER-encoded
355	CSSM_TIMESTRING 	verifyTime,		// may be NULL, implies "now"
356	CSSM_HANDLE_PTR		resultHand,		// RETURNED
357	CSSM_DATA_PTR		crl)			// RETURNED
358{
359	CSSM_QUERY						query;
360	CSSM_SELECTION_PREDICATE		pred[3];
361	CSSM_DB_UNIQUE_RECORD_PTR		record = NULL;
362	char							timeStr[CSSM_TIME_STRLEN + 1];
363
364	crl->Data = NULL;
365	crl->Length = 0;
366
367	/* Three predicates...first, the issuer name */
368	pred[0].DbOperator = CSSM_DB_EQUAL;
369	pred[0].Attribute.Info.AttributeNameFormat =
370		CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
371	pred[0].Attribute.Info.Label.AttributeName = (char*) "Issuer";
372	pred[0].Attribute.Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
373	pred[0].Attribute.Value = const_cast<CSSM_DATA_PTR>(issuerName);
374	pred[0].Attribute.NumberOfValues = 1;
375
376	/* now before/after. Cook up an appropriate time string. */
377	if(verifyTime != NULL) {
378		/* Caller spec'd tolerate any format */
379		int rtn = tpTimeToCssmTimestring(verifyTime, (unsigned)strlen(verifyTime), timeStr);
380		if(rtn) {
381			tpErrorLog("tpCrlLookup: Invalid VerifyTime string\n");
382			return NULL;
383		}
384	}
385	else {
386		/* right now */
387		StLock<Mutex> _(tpTimeLock());
388		timeAtNowPlus(0, TIME_CSSM, timeStr);
389	}
390	CSSM_DATA timeData;
391	timeData.Data = (uint8 *)timeStr;
392	timeData.Length = CSSM_TIME_STRLEN;
393
394	#if SEARCH_BY_DATE
395	pred[1].DbOperator = CSSM_DB_LESS_THAN;
396	pred[1].Attribute.Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
397	pred[1].Attribute.Info.Label.AttributeName = (char*) "NextUpdate";
398	pred[1].Attribute.Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
399	pred[1].Attribute.Value = &timeData;
400	pred[1].Attribute.NumberOfValues = 1;
401
402	pred[2].DbOperator = CSSM_DB_GREATER_THAN;
403	pred[2].Attribute.Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
404	pred[2].Attribute.Info.Label.AttributeName = (char*) "ThisUpdate";
405	pred[2].Attribute.Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
406	pred[2].Attribute.Value = &timeData;
407	pred[2].Attribute.NumberOfValues = 1;
408	#endif
409
410	query.RecordType = CSSM_DL_DB_RECORD_X509_CRL;
411	query.Conjunctive = CSSM_DB_AND;
412	#if SEARCH_BY_DATE
413	query.NumSelectionPredicates = 3;
414	#else
415	query.NumSelectionPredicates = 1;
416	#endif
417	query.SelectionPredicate = pred;
418	query.QueryLimits.TimeLimit = 0;	// FIXME - meaningful?
419	query.QueryLimits.SizeLimit = 1;	// FIXME - meaningful?
420	query.QueryFlags = 0;				// FIXME - used?
421
422	CSSM_DL_DataGetFirst(dlDb,
423		&query,
424		resultHand,
425		NULL,				// don't fetch attributes
426		crl,
427		&record);
428	return record;
429}
430
431/*
432 * Search a list of DBs for a CRL from the specified issuer and (optional)
433 * TPVerifyContext.verifyTime.
434 * Just a boolean return - we found it, or not. If we did, we return a
435 * TPCrlInfo which has been verified with the specified TPVerifyContext.
436 */
437TPCrlInfo *tpDbFindIssuerCrl(
438	TPVerifyContext		&vfyCtx,
439	const CSSM_DATA		&issuer,
440	TPCertInfo			&forCert)
441{
442	StLock<Mutex> _(SecTrustKeychainsGetMutex());
443
444	uint32						dbDex;
445	CSSM_HANDLE					resultHand;
446	CSSM_DATA					crl;
447	CSSM_DL_DB_HANDLE			dlDb;
448	CSSM_DB_UNIQUE_RECORD_PTR	record;
449	TPCrlInfo 					*issuerCrl = NULL;
450	CSSM_DL_DB_LIST_PTR 		dbList = vfyCtx.dbList;
451	CSSM_RETURN					crtn;
452
453	if(dbList == NULL) {
454		return NULL;
455	}
456	for(dbDex=0; dbDex<dbList->NumHandles; dbDex++) {
457		dlDb = dbList->DLDBHandle[dbDex];
458		crl.Data = NULL;
459		crl.Length = 0;
460		record = tpCrlLookup(dlDb,
461			&issuer,
462			vfyCtx.verifyTime,
463			&resultHand,
464			&crl);
465		/* remember we have to:
466		 * -- abort this query regardless, and
467		 * -- free the CSSM_DATA crl regardless, and
468		 * -- free the unique record if we don't use it
469		 *    (by placing it in issuerCert)...
470		 */
471		if(record != NULL) {
472			/* Found one */
473			assert(crl.Data != NULL);
474			issuerCrl = new TPCrlInfo(vfyCtx.clHand,
475				vfyCtx.cspHand,
476				&crl,
477				TIC_CopyData,
478				vfyCtx.verifyTime);
479			/* we're done with raw CRL data */
480			/* FIXME this assumes that vfyCtx.alloc is the same as the
481			 * allocator associated with DlDB...OK? */
482			tpFreeCssmData(vfyCtx.alloc, &crl, CSSM_FALSE);
483			crl.Data = NULL;
484			crl.Length = 0;
485
486			/* and we're done with the record */
487			CSSM_DL_FreeUniqueRecord(dlDb, record);
488
489			/* Does it verify with specified context? */
490			crtn = issuerCrl->verifyWithContextNow(vfyCtx, &forCert);
491			if(crtn) {
492
493				delete issuerCrl;
494				issuerCrl = NULL;
495
496				/*
497				 * Verify fail. Continue searching this DB. Break on
498				 * finding the holy grail or no more records found.
499				 */
500				for(;;) {
501					crl.Data = NULL;
502					crl.Length = 0;
503					crtn = CSSM_DL_DataGetNext(dlDb,
504						resultHand,
505						NULL,		// no attrs
506						&crl,
507						&record);
508					if(crtn) {
509						/* no more, done with this DB */
510						assert(crl.Data == NULL);
511						break;
512					}
513					assert(crl.Data != NULL);
514
515					/* found one - is it any good? */
516					issuerCrl = new TPCrlInfo(vfyCtx.clHand,
517						vfyCtx.cspHand,
518						&crl,
519						TIC_CopyData,
520						vfyCtx.verifyTime);
521					/* we're done with raw CRL data */
522					/* FIXME this assumes that vfyCtx.alloc is the same as the
523					* allocator associated with DlDB...OK? */
524					tpFreeCssmData(vfyCtx.alloc, &crl, CSSM_FALSE);
525					crl.Data = NULL;
526					crl.Length = 0;
527
528					CSSM_DL_FreeUniqueRecord(dlDb, record);
529
530					crtn = issuerCrl->verifyWithContextNow(vfyCtx, &forCert);
531					if(crtn == CSSM_OK) {
532						/* yes! */
533						break;
534					}
535					delete issuerCrl;
536					issuerCrl = NULL;
537				} /* searching subsequent records */
538			}	/* verify fail */
539			/* else success! */
540
541			if(issuerCrl != NULL) {
542				/* successful return */
543				CSSM_DL_DataAbortQuery(dlDb, resultHand);
544				tpDebug("tpDbFindIssuerCrl: found CRL record %p", record);
545				return issuerCrl;
546			}
547		}	/* tpCrlLookup, i.e., CSSM_DL_DataGetFirst, succeeded */
548		else {
549			assert(crl.Data == NULL);
550		}
551		/* in any case, abort the query for this db */
552		CSSM_DL_DataAbortQuery(dlDb, resultHand);
553
554	}	/* main loop searching dbList */
555
556	/* issuer not found */
557	return NULL;
558}
559
560