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