1/*
2 * Copyright (c) 2002-2011 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 * tpCrlVerify.cpp - routines to verify CRLs and to verify certs against CRLs.
21 */
22
23#include "tpCrlVerify.h"
24#include "TPCertInfo.h"
25#include "TPCrlInfo.h"
26#include "tpOcspVerify.h"
27#include "tpdebugging.h"
28#include "TPNetwork.h"
29#include "TPDatabase.h"
30#include <CommonCrypto/CommonDigest.h>
31#include <Security/oidscert.h>
32#include <security_ocspd/ocspdClient.h>
33#include <security_utilities/globalizer.h>
34#include <security_utilities/threading.h>
35#include <security_cdsa_utilities/cssmerrors.h>
36#include <sys/stat.h>
37
38/* general purpose, switch to policy-specific code based on TPVerifyContext.policy */
39CSSM_RETURN tpRevocationPolicyVerify(
40	TPVerifyContext	&tpVerifyContext,
41	TPCertGroup 	&certGroup)
42{
43	switch(tpVerifyContext.policy) {
44		case kRevokeNone:
45			return CSSM_OK;
46		case kRevokeCrlBasic:
47			return tpVerifyCertGroupWithCrls(tpVerifyContext, certGroup);
48		case kRevokeOcsp:
49			return tpVerifyCertGroupWithOCSP(tpVerifyContext, certGroup);
50		default:
51			assert(0);
52			return CSSMERR_TP_INTERNAL_ERROR;
53	}
54}
55
56/*
57 * For now, a process-wide memory resident CRL cache.
58 * We are responsible for deleting the CRLs which get added to this
59 * cache. Currently the only time we add a CRL to this cache is
60 * when we fetch one from the net. We ref count CRLs in this cache
61 * to allow multi-threaded access.
62 * Entries do not persist past the tpVerifyCertGroupWithCrls() in
63 * which they were created unless another thread in the same
64 * process snags a refcount (also from tpVerifyCertGroupWithCrls()).
65 * I.e. when cert verification is complete the cache will be empty.
66 * This is a change from Tiger and previous. CRLs get pretty big,
67 * up to a megabyte or so, and it's just not worth it to keep those
68 * around in memory. (OCSP responses, which are much smaller than
69 * CRLs, are indeed cached in memory. See tpOcspCache.cpp.)
70 */
71class TPCRLCache : private TPCrlGroup
72{
73public:
74	TPCRLCache();
75	~TPCRLCache() { }
76	TPCrlInfo *search(
77		TPCertInfo 			&cert,
78		TPVerifyContext		&vfyCtx);
79	void add(
80		TPCrlInfo 			&crl);
81	void remove(
82		TPCrlInfo 			&crl);
83	void release(
84		TPCrlInfo			&crl);
85
86private:
87	/* Protects ref count of all members of the cache */
88	Mutex 				mLock;
89};
90
91TPCRLCache::TPCRLCache()
92	: TPCrlGroup(Allocator::standard(), TGO_Group)
93{
94
95}
96
97TPCrlInfo *TPCRLCache::search(
98	TPCertInfo 			&cert,
99	TPVerifyContext		&vfyCtx)
100{
101	StLock<Mutex> _(mLock);
102	TPCrlInfo *crl = findCrlForCert(cert);
103	if(crl) {
104		/* reevaluate validity */
105		crl->calculateCurrent(vfyCtx.verifyTime);
106		crl->mRefCount++;
107		tpCrlDebug("TPCRLCache hit");
108	}
109	else {
110		tpCrlDebug("TPCRLCache miss");
111	}
112	return crl;
113}
114
115/* bumps ref count - caller is going to be using the CRL */
116void TPCRLCache::add(
117	TPCrlInfo 			&crl)
118{
119	StLock<Mutex> _(mLock);
120	tpCrlDebug("TPCRLCache add");
121	crl.mRefCount++;
122	appendCrl(crl);
123}
124
125/* delete and remove from cache if refCount zero */
126void TPCRLCache::release(
127	TPCrlInfo 			&crl)
128{
129	StLock<Mutex> _(mLock);
130	assert(crl.mRefCount > 0);
131	crl.mRefCount--;
132	if(crl.mRefCount == 0) {
133		tpCrlDebug("TPCRLCache release; deleting");
134		removeCrl(crl);
135		delete &crl;
136	}
137	else {
138		tpCrlDebug("TPCRLCache release; in use");
139	}
140}
141
142static ModuleNexus<TPCRLCache> tpGlobalCrlCache;
143
144/*
145 * Find CRL for specified cert. Only returns a fully verified CRL.
146 * Cert-specific errors such as CSSMERR_APPLETP_CRL_NOT_FOUND will be added
147 * to cert's return codes.
148 */
149static CSSM_RETURN tpFindCrlForCert(
150	TPCertInfo						&subject,
151	TPCrlInfo						*&foundCrl,		// RETURNED
152	TPVerifyContext					&vfyCtx)
153{
154
155	tpCrlDebug("tpFindCrlForCert top");
156	TPCrlInfo *crl = NULL;
157	foundCrl = NULL;
158	CSSM_APPLE_TP_CRL_OPT_FLAGS crlOptFlags = 0;
159
160	if(vfyCtx.crlOpts) {
161		crlOptFlags = vfyCtx.crlOpts->CrlFlags;
162	}
163
164	/* Search inputCrls for a CRL for subject cert */
165	if(vfyCtx.inputCrls != NULL) {
166		crl = vfyCtx.inputCrls->findCrlForCert(subject);
167		if(crl && (crl->verifyWithContextNow(vfyCtx, &subject) == CSSM_OK)) {
168			foundCrl = crl;
169			crl->mFromWhere = CFW_InGroup;
170			tpCrlDebug("   ...CRL found in CrlGroup");
171			return CSSM_OK;
172		}
173	}
174
175	/* local process-wide cache */
176	crl = tpGlobalCrlCache().search(subject, vfyCtx);
177	if(crl) {
178		tpCrlDebug("...tpFindCrlForCert found CRL in cache, calling verifyWithContext");
179		if(crl->verifyWithContextNow(vfyCtx, &subject) == CSSM_OK) {
180			foundCrl = crl;
181			crl->mFromWhere = CFW_LocalCache;
182			tpCrlDebug("   ...CRL found in local cache");
183			return CSSM_OK;
184		}
185		else {
186			tpGlobalCrlCache().release(*crl);
187		}
188	}
189
190	/*
191	 * Try DL/DB.
192	 * Note tpDbFindIssuerCrl() returns a verified CRL.
193	 */
194	crl = tpDbFindIssuerCrl(vfyCtx, *subject.issuerName(), subject);
195	if(crl) {
196		foundCrl = crl;
197		crl->mFromWhere = CFW_DlDb;
198		tpCrlDebug("   ...CRL found in DlDb");
199		return CSSM_OK;
200	}
201
202	/* Last resort: try net if enabled */
203	CSSM_RETURN crtn = CSSMERR_APPLETP_CRL_NOT_FOUND;
204	crl = NULL;
205	if(crlOptFlags & CSSM_TP_ACTION_FETCH_CRL_FROM_NET) {
206		crtn = tpFetchCrlFromNet(subject, vfyCtx, crl);
207	}
208
209	if(crtn) {
210		tpCrlDebug("   ...tpFindCrlForCert: CRL not found");
211		if(subject.addStatusCode(crtn)) {
212			return crtn;
213		}
214		else {
215			return CSSM_OK;
216		}
217	}
218
219	/* got one from net - add to global cache */
220	assert(crl != NULL);
221	tpGlobalCrlCache().add(*crl);
222	crl->mFromWhere = CFW_Net;
223	tpCrlDebug("   ...CRL found from net");
224
225	foundCrl = crl;
226	return CSSM_OK;
227}
228
229/*
230 * Dispose of a CRL obtained from tpFindCrlForCert().
231 */
232static void tpDisposeCrl(
233	TPCrlInfo			&crl,
234	TPVerifyContext		&vfyCtx)
235{
236	switch(crl.mFromWhere) {
237		case CFW_Nowhere:
238		default:
239			assert(0);
240			CssmError::throwMe(CSSMERR_TP_INTERNAL_ERROR);
241		case CFW_InGroup:
242			/* nothing to do, handled by TPCrlGroup */
243			return;
244		case CFW_DlDb:
245			/* cooked up specially for this call */
246			delete &crl;
247			return;
248		case CFW_LocalCache:		// cache hit
249		case CFW_Net:				// fetched from net & added to cache
250			tpGlobalCrlCache().release(crl);
251			return;
252		/* probably others */
253	}
254}
255
256/*
257 * Does this cert have a CrlDistributionPoints extension? We don't parse it, we
258 * just tell the caller whether or not it has one.
259 */
260static bool tpCertHasCrlDistPt(
261	TPCertInfo &cert)
262{
263	CSSM_DATA_PTR fieldValue;
264	CSSM_RETURN crtn = cert.fetchField(&CSSMOID_CrlDistributionPoints, &fieldValue);
265	if(crtn) {
266		return false;
267	}
268	else {
269		cert.freeField(&CSSMOID_CrlDistributionPoints,	fieldValue);
270		return true;
271	}
272}
273
274/*
275 * Get current CRL status for a certificate and its issuers.
276 *
277 * Possible results:
278 *
279 * CSSM_OK (we have a valid CRL; certificate is not revoked)
280 * CSSMERR_TP_CERT_REVOKED (we have a valid CRL; certificate is revoked)
281 * CSSMERR_APPLETP_NETWORK_FAILURE (CRL not available, download in progress)
282 * CSSMERR_APPLETP_CRL_NOT_FOUND (CRL not available, and not being fetched)
283 * CSSMERR_TP_INTERNAL_ERROR (unexpected error)
284 *
285 * Note that ocspdCRLStatus does NOT wait for the CRL to be downloaded before
286 * returning, nor does it initiate a CRL download.
287 */
288static
289CSSM_RETURN tpGetCrlStatusForCert(
290	TPCertInfo						&subject,
291	const CSSM_DATA					&issuers)
292{
293	CSSM_DATA *serialNumber=NULL;
294	CSSM_RETURN crtn = subject.fetchField(&CSSMOID_X509V1SerialNumber, &serialNumber);
295	if(crtn || !serialNumber) {
296		return CSSMERR_TP_INTERNAL_ERROR;
297	}
298	crtn = ocspdCRLStatus(*serialNumber, issuers, subject.issuerName(), NULL);
299	subject.freeField(&CSSMOID_X509V1SerialNumber, serialNumber);
300	return crtn;
301}
302
303/*
304 * Perform CRL verification on a cert group.
305 * The cert group has already passed basic issuer/subject and signature
306 * verification. The status of the incoming CRLs is completely unknown.
307 *
308 * FIXME - No mechanism to get CRLs from net with non-NULL verifyTime.
309 * How are we supposed to get the CRL which was valid at a specified
310 * time in the past?
311 */
312CSSM_RETURN tpVerifyCertGroupWithCrls(
313	TPVerifyContext					&vfyCtx,
314	TPCertGroup 					&certGroup)		// to be verified
315{
316	CSSM_RETURN 	crtn;
317	CSSM_RETURN		ourRtn = CSSM_OK;
318
319	assert(vfyCtx.clHand != 0);
320	assert(vfyCtx.policy == kRevokeCrlBasic);
321	tpCrlDebug("tpVerifyCertGroupWithCrls numCerts %u", certGroup.numCerts());
322	CSSM_DATA issuers = { 0, NULL };
323	CSSM_APPLE_TP_CRL_OPT_FLAGS optFlags = 0;
324	if(vfyCtx.crlOpts != NULL) {
325		optFlags = vfyCtx.crlOpts->CrlFlags;
326	}
327
328	/* found & verified CRLs we need to release */
329	TPCrlGroup foundCrls(vfyCtx.alloc, TGO_Caller);
330
331	try {
332
333		unsigned certDex;
334		TPCrlInfo *crl = NULL;
335
336		/* get issuers as PEM-encoded data blob; we need to release */
337		certGroup.encodeIssuers(issuers);
338
339		/* main loop, verify each cert */
340		for(certDex=0; certDex<certGroup.numCerts(); certDex++) {
341			TPCertInfo *cert = certGroup.certAtIndex(certDex);
342
343			tpCrlDebug("...verifying %s cert %u",
344				cert->isAnchor() ? "anchor " : "", cert->index());
345			if(cert->isSelfSigned() || cert->trustSettingsFound()) {
346				/* CRL meaningless for a root or trusted cert */
347				continue;
348			}
349			if(cert->revokeCheckComplete()) {
350				/* Another revocation policy claimed that this cert is good to go */
351				tpCrlDebug("   ...cert at index %u revokeCheckComplete; skipping",
352					cert->index());
353				continue;
354			}
355			crl = NULL;
356			do {
357				/* first, see if we have CRL status available for this cert */
358				crtn = tpGetCrlStatusForCert(*cert, issuers);
359				tpCrlDebug("...tpGetCrlStatusForCert: %u", crtn);
360				if(crtn == CSSM_OK) {
361					tpCrlDebug("tpVerifyCertGroupWithCrls: cert %u verified by local .crl\n",
362								cert->index());
363					cert->revokeCheckGood(true);
364					if(optFlags & CSSM_TP_ACTION_CRL_SUFFICIENT) {
365						/* no more revocation checking necessary for this cert */
366						cert->revokeCheckComplete(true);
367					}
368					break;
369				}
370				if(crtn == CSSMERR_TP_CERT_REVOKED) {
371					tpCrlDebug("tpVerifyCertGroupWithCrls: cert %u revoked in local .crl\n",
372								cert->index());
373					cert->addStatusCode(crtn);
374					break;
375				}
376				if(crtn == CSSMERR_APPLETP_NETWORK_FAILURE) {
377					/* crl is being fetched from net, but we don't have it yet */
378					if((optFlags & CSSM_TP_ACTION_REQUIRE_CRL_IF_PRESENT) &&
379								tpCertHasCrlDistPt(*cert)) {
380						/* crl is required; we don't have it yet, so we fail */
381						tpCrlDebug("   ...cert %u: REQUIRE_CRL_IF_PRESENT abort",
382								cert->index());
383						break;
384					}
385					/* "Best Attempt" case, so give the cert a pass for now */
386					tpCrlDebug("   ...cert %u: no CRL; tolerating", cert->index());
387					crtn = CSSM_OK;
388					break;
389				}
390				/* all other CRL status results: try to fetch the CRL */
391
392				/* find a CRL for this cert by hook or crook */
393				crtn = tpFindCrlForCert(*cert, crl, vfyCtx);
394				if(crtn) {
395					/* tpFindCrlForCert may have simply caused ocspd to start
396					 * downloading a CRL asynchronously; depending on the speed
397					 * of the network and the CRL size, this may return 0 bytes
398					 * of data with a CSSMERR_APPLETP_NETWORK_FAILURE result.
399					 * We won't know the actual revocation result until the
400					 * next time we call tpGetCrlStatusForCert after the full
401					 * CRL has been downloaded successfully.
402					 */
403					if(optFlags & CSSM_TP_ACTION_REQUIRE_CRL_PER_CERT) {
404						tpCrlDebug("   ...cert %u: REQUIRE_CRL_PER_CERT abort",
405								cert->index());
406						break;
407					}
408					if((optFlags & CSSM_TP_ACTION_REQUIRE_CRL_IF_PRESENT) &&
409								tpCertHasCrlDistPt(*cert)) {
410						tpCrlDebug("   ...cert %u: REQUIRE_CRL_IF_PRESENT abort",
411								cert->index());
412						break;
413					}
414					/*
415					 * This is the only place where "Best Attempt" tolerates an error
416					 */
417					tpCrlDebug("   ...cert %u: no CRL; tolerating", cert->index());
418					crtn = CSSM_OK;
419					assert(crl == NULL);
420					break;
421				}
422
423				/* Keep track; we'll release all when done. */
424				assert(crl != NULL);
425				foundCrls.appendCrl(*crl);
426
427				/* revoked? */
428				crtn = crl->isCertRevoked(*cert, vfyCtx.verifyTime);
429				if(crtn) {
430					break;
431				}
432				tpCrlDebug("   ...cert %u VERIFIED by CRL", cert->index());
433				cert->revokeCheckGood(true);
434				if(optFlags & CSSM_TP_ACTION_CRL_SUFFICIENT) {
435					/* no more revocation checking necessary for this cert */
436					cert->revokeCheckComplete(true);
437				}
438			} while(0);
439
440			/* done processing one cert */
441			if(crtn) {
442				tpCrlDebug("   ...cert at index %u FAILED crl vfy",
443					cert->index());
444				if(ourRtn == CSSM_OK) {
445					ourRtn = crtn;
446				}
447				/* continue on to next cert */
448			}	/* error on one cert */
449		}		/* for each cert */
450	}
451	catch(const CssmError &cerr) {
452		if(ourRtn == CSSM_OK) {
453			ourRtn = cerr.error;
454		}
455	}
456	/* other exceptions fatal */
457
458	/* release all found CRLs */
459	for(unsigned dex=0; dex<foundCrls.numCrls(); dex++) {
460		TPCrlInfo *crl = foundCrls.crlAtIndex(dex);
461		assert(crl != NULL);
462		tpDisposeCrl(*crl, vfyCtx);
463	}
464	/* release issuers */
465	if(issuers.Data) {
466		free(issuers.Data);
467	}
468	return ourRtn;
469}
470
471