]> git.saurik.com Git - apple/security.git/blame - libsecurity_apple_x509_tp/lib/tpCrlVerify.cpp
Security-55179.13.tar.gz
[apple/security.git] / libsecurity_apple_x509_tp / lib / tpCrlVerify.cpp
CommitLineData
b1ab9ed8
A
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 */
288CSSM_RETURN tpGetCrlStatusForCert(
289 TPCertInfo &subject,
290 const CSSM_DATA &issuers)
291{
292 CSSM_DATA *serialNumber=NULL;
293 CSSM_RETURN crtn = subject.fetchField(&CSSMOID_X509V1SerialNumber, &serialNumber);
294 if(crtn || !serialNumber) {
295 return CSSMERR_TP_INTERNAL_ERROR;
296 }
297 crtn = ocspdCRLStatus(*serialNumber, issuers, subject.issuerName(), NULL);
298 subject.freeField(&CSSMOID_X509V1SerialNumber, serialNumber);
299 return crtn;
300}
301
302/*
303 * Perform CRL verification on a cert group.
304 * The cert group has already passed basic issuer/subject and signature
305 * verification. The status of the incoming CRLs is completely unknown.
306 *
307 * FIXME - No mechanism to get CRLs from net with non-NULL verifyTime.
308 * How are we supposed to get the CRL which was valid at a specified
309 * time in the past?
310 */
311CSSM_RETURN tpVerifyCertGroupWithCrls(
312 TPVerifyContext &vfyCtx,
313 TPCertGroup &certGroup) // to be verified
314{
315 CSSM_RETURN crtn;
316 CSSM_RETURN ourRtn = CSSM_OK;
317
318 assert(vfyCtx.clHand != 0);
319 assert(vfyCtx.policy == kRevokeCrlBasic);
320 tpCrlDebug("tpVerifyCertGroupWithCrls numCerts %u", certGroup.numCerts());
321 CSSM_DATA issuers = { 0, NULL };
322 CSSM_APPLE_TP_CRL_OPT_FLAGS optFlags = 0;
323 if(vfyCtx.crlOpts != NULL) {
324 optFlags = vfyCtx.crlOpts->CrlFlags;
325 }
326
327 /* found & verified CRLs we need to release */
328 TPCrlGroup foundCrls(vfyCtx.alloc, TGO_Caller);
329
330 try {
331
332 unsigned certDex;
333 TPCrlInfo *crl = NULL;
334
335 /* get issuers as PEM-encoded data blob; we need to release */
336 certGroup.encodeIssuers(issuers);
337
338 /* main loop, verify each cert */
339 for(certDex=0; certDex<certGroup.numCerts(); certDex++) {
340 TPCertInfo *cert = certGroup.certAtIndex(certDex);
341
342 tpCrlDebug("...verifying %s cert %u",
343 cert->isAnchor() ? "anchor " : "", cert->index());
344 if(cert->isSelfSigned() || cert->trustSettingsFound()) {
345 /* CRL meaningless for a root or trusted cert */
346 continue;
347 }
348 if(cert->revokeCheckComplete()) {
349 /* Another revocation policy claimed that this cert is good to go */
350 tpCrlDebug(" ...cert at index %u revokeCheckComplete; skipping",
351 cert->index());
352 continue;
353 }
354 crl = NULL;
355 do {
356 /* first, see if we have CRL status available for this cert */
357 crtn = tpGetCrlStatusForCert(*cert, issuers);
358 tpCrlDebug("...tpGetCrlStatusForCert: %u", crtn);
359 if(crtn == CSSM_OK) {
360 tpCrlDebug("tpVerifyCertGroupWithCrls: cert %u verified by local .crl\n",
361 cert->index());
362 cert->revokeCheckGood(true);
363 if(optFlags & CSSM_TP_ACTION_CRL_SUFFICIENT) {
364 /* no more revocation checking necessary for this cert */
365 cert->revokeCheckComplete(true);
366 }
367 break;
368 }
369 if(crtn == CSSMERR_TP_CERT_REVOKED) {
370 tpCrlDebug("tpVerifyCertGroupWithCrls: cert %u revoked in local .crl\n",
371 cert->index());
372 cert->addStatusCode(crtn);
373 break;
374 }
375 if(crtn == CSSMERR_APPLETP_NETWORK_FAILURE) {
376 /* crl is being fetched from net, but we don't have it yet */
377 if((optFlags & CSSM_TP_ACTION_REQUIRE_CRL_IF_PRESENT) &&
378 tpCertHasCrlDistPt(*cert)) {
379 /* crl is required; we don't have it yet, so we fail */
380 tpCrlDebug(" ...cert %u: REQUIRE_CRL_IF_PRESENT abort",
381 cert->index());
382 break;
383 }
384 /* "Best Attempt" case, so give the cert a pass for now */
385 tpCrlDebug(" ...cert %u: no CRL; tolerating", cert->index());
386 crtn = CSSM_OK;
387 break;
388 }
389 /* all other CRL status results: try to fetch the CRL */
390
391 /* find a CRL for this cert by hook or crook */
392 crtn = tpFindCrlForCert(*cert, crl, vfyCtx);
393 if(crtn) {
394 /* tpFindCrlForCert may have simply caused ocspd to start
395 * downloading a CRL asynchronously; depending on the speed
396 * of the network and the CRL size, this may return 0 bytes
397 * of data with a CSSMERR_APPLETP_NETWORK_FAILURE result.
398 * We won't know the actual revocation result until the
399 * next time we call tpGetCrlStatusForCert after the full
400 * CRL has been downloaded successfully.
401 */
402 if(optFlags & CSSM_TP_ACTION_REQUIRE_CRL_PER_CERT) {
403 tpCrlDebug(" ...cert %u: REQUIRE_CRL_PER_CERT abort",
404 cert->index());
405 break;
406 }
407 if((optFlags & CSSM_TP_ACTION_REQUIRE_CRL_IF_PRESENT) &&
408 tpCertHasCrlDistPt(*cert)) {
409 tpCrlDebug(" ...cert %u: REQUIRE_CRL_IF_PRESENT abort",
410 cert->index());
411 break;
412 }
413 /*
414 * This is the only place where "Best Attempt" tolerates an error
415 */
416 tpCrlDebug(" ...cert %u: no CRL; tolerating", cert->index());
417 crtn = CSSM_OK;
418 assert(crl == NULL);
419 break;
420 }
421
422 /* Keep track; we'll release all when done. */
423 assert(crl != NULL);
424 foundCrls.appendCrl(*crl);
425
426 /* revoked? */
427 crtn = crl->isCertRevoked(*cert, vfyCtx.verifyTime);
428 if(crtn) {
429 break;
430 }
431 tpCrlDebug(" ...cert %u VERIFIED by CRL", cert->index());
432 cert->revokeCheckGood(true);
433 if(optFlags & CSSM_TP_ACTION_CRL_SUFFICIENT) {
434 /* no more revocation checking necessary for this cert */
435 cert->revokeCheckComplete(true);
436 }
437 } while(0);
438
439 /* done processing one cert */
440 if(crtn) {
441 tpCrlDebug(" ...cert at index %u FAILED crl vfy",
442 cert->index());
443 if(ourRtn == CSSM_OK) {
444 ourRtn = crtn;
445 }
446 /* continue on to next cert */
447 } /* error on one cert */
448 } /* for each cert */
449 }
450 catch(const CssmError &cerr) {
451 if(ourRtn == CSSM_OK) {
452 ourRtn = cerr.error;
453 }
454 }
455 /* other exceptions fatal */
456
457 /* release all found CRLs */
458 for(unsigned dex=0; dex<foundCrls.numCrls(); dex++) {
459 TPCrlInfo *crl = foundCrls.crlAtIndex(dex);
460 assert(crl != NULL);
461 tpDisposeCrl(*crl, vfyCtx);
462 }
463 /* release issuers */
464 if(issuers.Data) {
465 free(issuers.Data);
466 }
467 return ourRtn;
468}
469