diff options
author | kaie%kuix.de <devnull@localhost> | 2007-04-25 01:05:07 +0000 |
---|---|---|
committer | kaie%kuix.de <devnull@localhost> | 2007-04-25 01:05:07 +0000 |
commit | 286e08474b3d3e189463d8d6e78c32469447961e (patch) | |
tree | 4c1984acfbd9a78a04bda10fcb68949371ceb311 | |
parent | 45105225a2252b61b132b02d8a370a88f557f16a (diff) | |
download | nss-hg-286e08474b3d3e189463d8d6e78c32469447961e.tar.gz |
Bug 205406, Need a local OCSP cache
r=nelson, r=rrelyea
-rw-r--r-- | security/nss/lib/certhigh/ocsp.c | 1319 | ||||
-rw-r--r-- | security/nss/lib/certhigh/ocsp.h | 31 | ||||
-rw-r--r-- | security/nss/lib/certhigh/ocspi.h | 3 | ||||
-rw-r--r-- | security/nss/lib/certhigh/ocspt.h | 23 | ||||
-rw-r--r-- | security/nss/lib/nss/nss.def | 8 | ||||
-rw-r--r-- | security/nss/lib/nss/nssinit.c | 3 |
6 files changed, 1232 insertions, 155 deletions
diff --git a/security/nss/lib/certhigh/ocsp.c b/security/nss/lib/certhigh/ocsp.c index 5fab28928..55d8baf90 100644 --- a/security/nss/lib/certhigh/ocsp.c +++ b/security/nss/lib/certhigh/ocsp.c @@ -19,6 +19,7 @@ * the Initial Developer. All Rights Reserved. * * Contributor(s): + * Kai Engert (kengert@redhat.com) * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or @@ -66,39 +67,829 @@ #include "certxutl.h" #include "pk11func.h" /* for PK11_HashBuf */ #include <stdarg.h> +#include <plhash.h> +#define DEFAULT_OCSP_CACHE_SIZE 1000 +#define DEFAULT_MINIMUM_SECONDS_TO_NEXT_OCSP_FETCH_ATTEMPT 1*60*60L +#define DEFAULT_MAXIMUM_SECONDS_TO_NEXT_OCSP_FETCH_ATTEMPT 24*60*60L +#define MICROSECONDS_PER_SECOND 1000000L + +typedef struct OCSPCacheItemStr OCSPCacheItem; +typedef struct OCSPCacheDataStr OCSPCacheData; + +struct OCSPCacheItemStr { + /* LRU linking */ + OCSPCacheItem *moreRecent; + OCSPCacheItem *lessRecent; + + /* key */ + CERTOCSPCertID *certID; + /* CertID's arena also used to allocate "this" cache item */ + + /* cache control information */ + PRTime nextFetchAttemptTime; + + /* Cached contents. Use a separate arena, because lifetime is different */ + PRArenaPool *certStatusArena; /* NULL means: no cert status cached */ + ocspCertStatus certStatus; + + PRPackedBool haveThisUpdate; + PRPackedBool haveNextUpdate; + PRTime thisUpdate; + PRTime nextUpdate; +}; + +struct OCSPCacheDataStr { + PLHashTable *entries; + PRUint32 numberOfEntries; + OCSPCacheItem *MRUitem; /* most recently used cache item */ + OCSPCacheItem *LRUitem; /* least recently used cache item */ +}; static struct OCSPGlobalStruct { - PRLock *lock; + PRMonitor *monitor; const SEC_HttpClientFcn *defaultHttpClientFcn; -} OCSP_Global = { NULL, NULL }; + PRInt32 maxCacheEntries; + PRUint32 minimumSecondsToNextFetchAttempt; + PRUint32 maximumSecondsToNextFetchAttempt; + OCSPCacheData cache; + SEC_OcspFailureMode ocspFailureMode; +} OCSP_Global = { NULL, + NULL, + DEFAULT_OCSP_CACHE_SIZE, + DEFAULT_MINIMUM_SECONDS_TO_NEXT_OCSP_FETCH_ATTEMPT, + DEFAULT_MAXIMUM_SECONDS_TO_NEXT_OCSP_FETCH_ATTEMPT, + {NULL, 0, NULL, NULL}, + ocspMode_FailureIsVerificationFailure + }; + +/* Forward declarations */ +static SECItem * +ocsp_GetEncodedOCSPResponseFromRequest(PRArenaPool *arena, + CERTOCSPRequest *request, + char *location, int64 time, + PRBool addServiceLocator, + void *pwArg, + CERTOCSPRequest **pRequest); +static SECStatus +ocsp_GetOCSPStatusFromNetwork(CERTCertDBHandle *handle, + CERTOCSPCertID *certID, + CERTCertificate *cert, + int64 time, + void *pwArg, + PRBool *certIDWasConsumed, + SECStatus *rv_ocsp); +static SECStatus +ocsp_GetVerifiedSingleResponseForCertID(CERTCertDBHandle *handle, + CERTOCSPResponse *response, + CERTOCSPCertID *certID, + CERTCertificate *signerCert, + int64 time, + CERTOCSPSingleResponse **pSingleResponse); + +#ifndef DEBUG +#define OCSP_TRACE(msg) +#define OCSP_TRACE_TIME(msg, time) +#define OCSP_TRACE_CERT(cert) +#else +#define OCSP_TRACE(msg) ocsp_Trace msg +#define OCSP_TRACE_TIME(msg, time) ocsp_dumpStringWithTime(msg, time) +#define OCSP_TRACE_CERT(cert) dumpCertificate(cert) + +#if (defined(XP_UNIX) || defined(XP_WIN32) || defined(XP_BEOS) \ + || defined(XP_MACOSX)) && !defined(_WIN32_WCE) +#define NSS_HAVE_GETENV 1 +#endif + +static PRBool wantOcspTrace() +{ + static PRBool firstTime = PR_TRUE; + static PRBool wantTrace = PR_FALSE; + +#ifdef NSS_HAVE_GETENV + if (firstTime) { + char *ev = getenv("NSS_TRACE_OCSP"); + if (ev && ev[0]) { + wantTrace = PR_TRUE; + } + firstTime = PR_FALSE; + } +#endif + return wantTrace; +} + +static void +ocsp_Trace(const char *format, ...) +{ + char buf[2000]; + va_list args; + + if (!wantOcspTrace()) + return; + va_start(args, format); + PR_vsnprintf(buf, sizeof(buf), format, args); + va_end(args); + PR_LogPrint("%s", buf); +} + +static void +ocsp_dumpStringWithTime(const char *str, int64 time) +{ + PRExplodedTime timePrintable; + char timestr[100]; + + if (!wantOcspTrace()) + return; + PR_ExplodeTime(time, PR_GMTParameters, &timePrintable); + PR_FormatTime(timestr, 100, "%a %b %d %H:%M:%S %Y", + &timePrintable); + ocsp_Trace("OCSP %s %s\n", str, timestr); +} + +static void +printHexString(const char *prefix, SECItem *hexval) +{ + unsigned int i; + char *hexbuf = NULL; + + for (i = 0; i < hexval->len; i++) { + if (i != hexval->len - 1) { + PR_sprintf_append(hexbuf, "%02x:", hexval->data[i]); + } else { + PR_sprintf_append(hexbuf, "%02x", hexval->data[i]); + } + } + if (hexbuf) { + ocsp_Trace("%s %s\n", prefix, hexbuf); + PR_smprintf_free(hexbuf); + } +} + +static void +dumpCertificate(CERTCertificate *cert) +{ + if (!wantOcspTrace()) + return; + + ocsp_Trace("OCSP ----------------\n"); + ocsp_Trace("OCSP ## SUBJECT: %s\n", cert->subjectName); + { + int64 timeBefore, timeAfter; + PRExplodedTime beforePrintable, afterPrintable; + char beforestr[100], afterstr[100]; + DER_DecodeTimeChoice(&timeBefore, &cert->validity.notBefore); + DER_DecodeTimeChoice(&timeAfter, &cert->validity.notAfter); + PR_ExplodeTime(timeBefore, PR_GMTParameters, &beforePrintable); + PR_ExplodeTime(timeAfter, PR_GMTParameters, &afterPrintable); + PR_FormatTime(beforestr, 100, "%a %b %d %H:%M:%S %Y", + &beforePrintable); + PR_FormatTime(afterstr, 100, "%a %b %d %H:%M:%S %Y", + &afterPrintable); + ocsp_Trace("OCSP ## VALIDITY: %s to %s\n", beforestr, afterstr); + } + ocsp_Trace("OCSP ## ISSUER: %s\n", cert->issuerName); + printHexString("OCSP ## SERIAL NUMBER:", &cert->serialNumber); +} +#endif SECStatus SEC_RegisterDefaultHttpClient(const SEC_HttpClientFcn *fcnTable) { - if (!OCSP_Global.lock) { + if (!OCSP_Global.monitor) { PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return SECFailure; } - PR_Lock(OCSP_Global.lock); + PR_EnterMonitor(OCSP_Global.monitor); OCSP_Global.defaultHttpClientFcn = fcnTable; - PR_Unlock(OCSP_Global.lock); + PR_ExitMonitor(OCSP_Global.monitor); return SECSuccess; } -/* this function is called at NSS initialization time */ -SECStatus InitOCSPGlobal(void) +static PLHashNumber PR_CALLBACK +ocsp_CacheKeyHashFunction(const void *key) +{ + CERTOCSPCertID *cid = (CERTOCSPCertID *)key; + PLHashNumber hash = 0; + unsigned int i; + unsigned char *walk; + + /* a very simple hash calculation for the initial coding phase */ + walk = (unsigned char*)cid->issuerNameHash.data; + for (i=0; i < cid->issuerNameHash.len; ++i, ++walk) { + hash += *walk; + } + walk = (unsigned char*)cid->issuerKeyHash.data; + for (i=0; i < cid->issuerKeyHash.len; ++i, ++walk) { + hash += *walk; + } + walk = (unsigned char*)cid->serialNumber.data; + for (i=0; i < cid->serialNumber.len; ++i, ++walk) { + hash += *walk; + } + return hash; +} + +static PRIntn PR_CALLBACK +ocsp_CacheKeyCompareFunction(const void *v1, const void *v2) +{ + CERTOCSPCertID *cid1 = (CERTOCSPCertID *)v1; + CERTOCSPCertID *cid2 = (CERTOCSPCertID *)v2; + + return (SECEqual == SECITEM_CompareItem(&cid1->issuerNameHash, + &cid2->issuerNameHash) + && SECEqual == SECITEM_CompareItem(&cid1->issuerKeyHash, + &cid2->issuerKeyHash) + && SECEqual == SECITEM_CompareItem(&cid1->serialNumber, + &cid2->serialNumber)); +} + +static SECStatus +ocsp_CopyRevokedInfo(PRArenaPool *arena, ocspCertStatus *dest, + ocspRevokedInfo *src) { - if (OCSP_Global.lock != NULL) { - /* already initialized */ + SECStatus rv = SECFailure; + void *mark; + + mark = PORT_ArenaMark(arena); + + dest->certStatusInfo.revokedInfo = + (ocspRevokedInfo *) PORT_ArenaZAlloc(arena, sizeof(ocspRevokedInfo)); + if (!dest->certStatusInfo.revokedInfo) { + goto loser; + } + + rv = SECITEM_CopyItem(arena, + &dest->certStatusInfo.revokedInfo->revocationTime, + &src->revocationTime); + if (rv != SECSuccess) { + goto loser; + } + + if (src->revocationReason) { + dest->certStatusInfo.revokedInfo->revocationReason = + SECITEM_ArenaDupItem(arena, src->revocationReason); + if (!dest->certStatusInfo.revokedInfo->revocationReason) { + goto loser; + } + } else { + dest->certStatusInfo.revokedInfo->revocationReason = NULL; + } + + PORT_ArenaUnmark(arena, mark); return SECSuccess; - } + +loser: + PORT_ArenaRelease(arena, mark); + return SECFailure; +} + +static SECStatus +ocsp_CopyCertStatus(PRArenaPool *arena, ocspCertStatus *dest, + ocspCertStatus*src) +{ + SECStatus rv = SECFailure; + dest->certStatusType = src->certStatusType; + + switch (src->certStatusType) { + case ocspCertStatus_good: + dest->certStatusInfo.goodInfo = + SECITEM_ArenaDupItem(arena, src->certStatusInfo.goodInfo); + if (dest->certStatusInfo.goodInfo != NULL) { + rv = SECSuccess; + } + break; + case ocspCertStatus_revoked: + rv = ocsp_CopyRevokedInfo(arena, dest, + src->certStatusInfo.revokedInfo); + break; + case ocspCertStatus_unknown: + dest->certStatusInfo.unknownInfo = + SECITEM_ArenaDupItem(arena, src->certStatusInfo.unknownInfo); + if (dest->certStatusInfo.unknownInfo != NULL) { + rv = SECSuccess; + } + break; + case ocspCertStatus_other: + default: + PORT_Assert(src->certStatusType == ocspCertStatus_other); + dest->certStatusInfo.otherInfo = + SECITEM_ArenaDupItem(arena, src->certStatusInfo.otherInfo); + if (dest->certStatusInfo.otherInfo != NULL) { + rv = SECSuccess; + } + break; + } + return rv; +} + +static void +ocsp_AddCacheItemToLinkedList(OCSPCacheData *cache, OCSPCacheItem *new_most_recent) +{ + PR_EnterMonitor(OCSP_Global.monitor); + + if (!cache->LRUitem) { + cache->LRUitem = new_most_recent; + } + new_most_recent->lessRecent = cache->MRUitem; + new_most_recent->moreRecent = NULL; + + if (cache->MRUitem) { + cache->MRUitem->moreRecent = new_most_recent; + } + cache->MRUitem = new_most_recent; + + PR_ExitMonitor(OCSP_Global.monitor); +} + +static void +ocsp_RemoveCacheItemFromLinkedList(OCSPCacheData *cache, OCSPCacheItem *item) +{ + PR_EnterMonitor(OCSP_Global.monitor); + + if (!item->lessRecent && !item->moreRecent) { + /* + * Fail gracefully on attempts to remove an item from the list, + * which is currently not part of the list. + * But check for the edge case it is the single entry in the list. + */ + if (item == cache->LRUitem && + item == cache->MRUitem) { + /* remove the single entry */ + PORT_Assert(cache->numberOfEntries == 1); + PORT_Assert(item->moreRecent == NULL); + cache->MRUitem = NULL; + cache->LRUitem = NULL; + } + PR_ExitMonitor(OCSP_Global.monitor); + return; + } + + PORT_Assert(cache->numberOfEntries > 1); + + if (item == cache->LRUitem) { + PORT_Assert(item != cache->MRUitem); + PORT_Assert(item->lessRecent == NULL); + PORT_Assert(item->moreRecent != NULL); + PORT_Assert(item->moreRecent->lessRecent == item); + cache->LRUitem = item->moreRecent; + cache->LRUitem->lessRecent = NULL; + } + else if (item == cache->MRUitem) { + PORT_Assert(item->moreRecent == NULL); + PORT_Assert(item->lessRecent != NULL); + PORT_Assert(item->lessRecent->moreRecent == item); + cache->MRUitem = item->lessRecent; + cache->MRUitem->moreRecent = NULL; + } else { + /* remove an entry in the middle of the list */ + PORT_Assert(item->moreRecent != NULL); + PORT_Assert(item->lessRecent != NULL); + PORT_Assert(item->lessRecent->moreRecent == item); + PORT_Assert(item->moreRecent->lessRecent == item); + item->moreRecent->lessRecent = item->lessRecent; + item->lessRecent->moreRecent = item->moreRecent; + } + + item->lessRecent = NULL; + item->moreRecent = NULL; + + PR_ExitMonitor(OCSP_Global.monitor); +} + +static void +ocsp_MakeCacheEntryMostRecent(OCSPCacheData *cache, OCSPCacheItem *new_most_recent) +{ + OCSP_TRACE(("OCSP ocsp_MakeCacheEntryMostRecent THREADID %p\n", + PR_GetCurrentThread())); + PR_EnterMonitor(OCSP_Global.monitor); + if (cache->MRUitem == new_most_recent) { + OCSP_TRACE(("OCSP ocsp_MakeCacheEntryMostRecent ALREADY MOST\n")); + PR_ExitMonitor(OCSP_Global.monitor); + return; + } + OCSP_TRACE(("OCSP ocsp_MakeCacheEntryMostRecent NEW entry\n")); + ocsp_RemoveCacheItemFromLinkedList(cache, new_most_recent); + ocsp_AddCacheItemToLinkedList(cache, new_most_recent); + PR_ExitMonitor(OCSP_Global.monitor); +} + +static PRBool +ocsp_IsCacheDisabled() +{ + /* + * maxCacheEntries == 0 means unlimited cache entries + * maxCacheEntries < 0 means cache is disabled + */ + PRBool retval; + PR_EnterMonitor(OCSP_Global.monitor); + retval = (OCSP_Global.maxCacheEntries < 0); + PR_ExitMonitor(OCSP_Global.monitor); + return retval; +} + +static OCSPCacheItem * +ocsp_FindCacheEntry(OCSPCacheData *cache, CERTOCSPCertID *certID) +{ + OCSPCacheItem *found_ocsp_item = NULL; + OCSP_TRACE(("OCSP ocsp_FindCacheEntry\n")); + PR_EnterMonitor(OCSP_Global.monitor); + if (ocsp_IsCacheDisabled()) + goto loser; - OCSP_Global.lock = PR_NewLock(); + found_ocsp_item = (OCSPCacheItem *)PL_HashTableLookup( + cache->entries, certID); + if (!found_ocsp_item) + goto loser; - return (OCSP_Global.lock) ? SECSuccess : SECFailure; + OCSP_TRACE(("OCSP ocsp_FindCacheEntry FOUND!\n")); + ocsp_MakeCacheEntryMostRecent(cache, found_ocsp_item); + +loser: + PR_ExitMonitor(OCSP_Global.monitor); + return found_ocsp_item; +} + +static void +ocsp_FreeCacheItem(OCSPCacheItem *item) +{ + OCSP_TRACE(("OCSP ocsp_FreeCacheItem\n")); + if (item->certStatusArena) { + PORT_FreeArena(item->certStatusArena, PR_FALSE); + } + if (item->certID->poolp) { + /* freeing this poolp arena will also free item */ + PORT_FreeArena(item->certID->poolp, PR_FALSE); + } +} + +static void +ocsp_RemoveCacheItem(OCSPCacheData *cache, OCSPCacheItem *item) +{ + /* The item we're removing could be either the least recently used item, + * or it could be an item that couldn't get updated with newer status info + * because of an allocation failure, or it could get removed because we're + * cleaning up. + */ + PRBool couldRemoveFromHashTable; + OCSP_TRACE(("OCSP ocsp_RemoveCacheItem, THREADID %p\n", PR_GetCurrentThread())); + PR_EnterMonitor(OCSP_Global.monitor); + + ocsp_RemoveCacheItemFromLinkedList(cache, item); + couldRemoveFromHashTable = PL_HashTableRemove(cache->entries, + item->certID); + PORT_Assert(couldRemoveFromHashTable); + --cache->numberOfEntries; + ocsp_FreeCacheItem(item); + PR_ExitMonitor(OCSP_Global.monitor); +} + +static void +ocsp_CheckCacheSize(OCSPCacheData *cache) +{ + OCSP_TRACE(("OCSP ocsp_CheckCacheSize\n")); + PR_EnterMonitor(OCSP_Global.monitor); + if (OCSP_Global.maxCacheEntries <= 0) /* disabled or unlimited */ + return; + while (cache->numberOfEntries > OCSP_Global.maxCacheEntries) { + ocsp_RemoveCacheItem(cache, cache->LRUitem); + } + PR_ExitMonitor(OCSP_Global.monitor); +} + +SECStatus +CERT_ClearOCSPCache() +{ + OCSP_TRACE(("OCSP CERT_ClearOCSPCache\n")); + PR_EnterMonitor(OCSP_Global.monitor); + while (OCSP_Global.cache.numberOfEntries > 0) { + ocsp_RemoveCacheItem(&OCSP_Global.cache, + OCSP_Global.cache.LRUitem); + } + PR_ExitMonitor(OCSP_Global.monitor); + return SECSuccess; +} + +static SECStatus +ocsp_CreateCacheItemAndConsumeCertID(OCSPCacheData *cache, + CERTOCSPCertID *certID, + OCSPCacheItem **pCacheItem) +{ + PRArenaPool *arena; + void *mark; + PLHashEntry *new_hash_entry; + OCSPCacheItem *item; + + PORT_Assert(pCacheItem != NULL); + *pCacheItem = NULL; + + PR_EnterMonitor(OCSP_Global.monitor); + arena = certID->poolp; + mark = PORT_ArenaMark(arena); + + /* ZAlloc will init all Bools to False and all Pointers to NULL */ + item = (OCSPCacheItem *)PORT_ArenaZAlloc(certID->poolp, + sizeof(OCSPCacheItem)); + if (!item) { + goto loser; + } + item->certID = certID; + new_hash_entry = PL_HashTableAdd(cache->entries, item->certID, + item); + if (!new_hash_entry) { + goto loser; + } + ++cache->numberOfEntries; + PORT_ArenaUnmark(arena, mark); + ocsp_AddCacheItemToLinkedList(cache, item); + *pCacheItem = item; + + PR_ExitMonitor(OCSP_Global.monitor); + return SECSuccess; + +loser: + PORT_ArenaRelease(arena, mark); + PR_ExitMonitor(OCSP_Global.monitor); + return SECFailure; +} + +static SECStatus +ocsp_SetCacheItemResponse(OCSPCacheItem *item, + const CERTOCSPSingleResponse *response) +{ + if (item->certStatusArena) { + PORT_FreeArena(item->certStatusArena, PR_FALSE); + item->certStatusArena = NULL; + } + item->haveThisUpdate = item->haveNextUpdate = PR_FALSE; + if (response) { + SECStatus rv; + item->certStatusArena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (item->certStatusArena == NULL) { + return SECFailure; + } + rv = ocsp_CopyCertStatus(item->certStatusArena, &item->certStatus, + response->certStatus); + if (rv != SECSuccess) { + PORT_FreeArena(item->certStatusArena, PR_FALSE); + item->certStatusArena = NULL; + return rv; + } + rv = DER_GeneralizedTimeToTime(&item->thisUpdate, + &response->thisUpdate); + item->haveThisUpdate = (rv == SECSuccess); + if (response->nextUpdate) { + rv = DER_GeneralizedTimeToTime(&item->nextUpdate, + response->nextUpdate); + item->haveNextUpdate = (rv == SECSuccess); + } else { + item->haveNextUpdate = PR_FALSE; + } + } + return SECSuccess; +} + +static void +ocsp_FreshenCacheItemNextFetchAttemptTime(OCSPCacheItem *cacheItem) +{ + PRTime now; + PRTime earliestAllowedNextFetchAttemptTime; + PRTime latestTimeWhenResponseIsConsideredFresh; + + OCSP_TRACE(("OCSP ocsp_FreshenCacheItemNextFetchAttemptTime\n")); + + PR_EnterMonitor(OCSP_Global.monitor); + + now = PR_Now(); + OCSP_TRACE_TIME("now:", now); + + if (cacheItem->haveThisUpdate) { + OCSP_TRACE_TIME("thisUpdate:", cacheItem->thisUpdate); + latestTimeWhenResponseIsConsideredFresh = cacheItem->thisUpdate + + OCSP_Global.maximumSecondsToNextFetchAttempt * + MICROSECONDS_PER_SECOND; + OCSP_TRACE_TIME("latestTimeWhenResponseIsConsideredFresh:", + latestTimeWhenResponseIsConsideredFresh); + } else { + latestTimeWhenResponseIsConsideredFresh = now + + OCSP_Global.minimumSecondsToNextFetchAttempt * + MICROSECONDS_PER_SECOND; + OCSP_TRACE_TIME("no thisUpdate, " + "latestTimeWhenResponseIsConsideredFresh:", + latestTimeWhenResponseIsConsideredFresh); + } + + if (cacheItem->haveNextUpdate) { + OCSP_TRACE_TIME("have nextUpdate:", cacheItem->thisUpdate); + } + + if (cacheItem->haveNextUpdate && + cacheItem->nextUpdate < latestTimeWhenResponseIsConsideredFresh) { + latestTimeWhenResponseIsConsideredFresh = cacheItem->nextUpdate; + OCSP_TRACE_TIME("nextUpdate is smaller than latestFresh, setting " + "latestTimeWhenResponseIsConsideredFresh:", + latestTimeWhenResponseIsConsideredFresh); + } + + earliestAllowedNextFetchAttemptTime = now + + OCSP_Global.minimumSecondsToNextFetchAttempt * + MICROSECONDS_PER_SECOND; + OCSP_TRACE_TIME("earliestAllowedNextFetchAttemptTime:", + earliestAllowedNextFetchAttemptTime); + + if (latestTimeWhenResponseIsConsideredFresh < + earliestAllowedNextFetchAttemptTime) { + latestTimeWhenResponseIsConsideredFresh = + earliestAllowedNextFetchAttemptTime; + OCSP_TRACE_TIME("latest < earliest, setting latest to:", + latestTimeWhenResponseIsConsideredFresh); + } + + cacheItem->nextFetchAttemptTime = + latestTimeWhenResponseIsConsideredFresh; + OCSP_TRACE_TIME("nextFetchAttemptTime", + latestTimeWhenResponseIsConsideredFresh); + + PR_ExitMonitor(OCSP_Global.monitor); +} + +static PRBool +ocsp_IsCacheItemFresh(OCSPCacheItem *cacheItem) +{ + PRTime now; + PRBool retval; + + PR_EnterMonitor(OCSP_Global.monitor); + now = PR_Now(); + retval = (cacheItem->nextFetchAttemptTime > now); + OCSP_TRACE(("OCSP ocsp_IsCacheItemFresh: %d\n", retval)); + PR_ExitMonitor(OCSP_Global.monitor); + return retval; +} + +/* + * Status in *certIDWasConsumed will always be correct, regardless of + * return value. + */ +static SECStatus +ocsp_CreateOrUpdateCacheEntry(OCSPCacheData *cache, + CERTOCSPCertID *certID, + CERTOCSPSingleResponse *single, + PRBool *certIDWasConsumed) +{ + SECStatus rv; + OCSPCacheItem *cacheItem; + OCSP_TRACE(("OCSP ocsp_CreateOrUpdateCacheEntry\n")); + + if (!certIDWasConsumed) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + *certIDWasConsumed = PR_FALSE; + + PR_EnterMonitor(OCSP_Global.monitor); + PORT_Assert(OCSP_Global.maxCacheEntries >= 0); + + cacheItem = ocsp_FindCacheEntry(cache, certID); + if (!cacheItem) { + rv = ocsp_CreateCacheItemAndConsumeCertID(cache, certID, + &cacheItem); + if (rv != SECSuccess) { + PR_ExitMonitor(OCSP_Global.monitor); + return rv; + } + *certIDWasConsumed = PR_TRUE; + } + if (single) { + rv = ocsp_SetCacheItemResponse(cacheItem, single); + if (rv != SECSuccess) { + ocsp_RemoveCacheItem(cache, cacheItem); + PR_ExitMonitor(OCSP_Global.monitor); + return rv; + } + } + ocsp_FreshenCacheItemNextFetchAttemptTime(cacheItem); + ocsp_CheckCacheSize(cache); + + PR_ExitMonitor(OCSP_Global.monitor); + return SECSuccess; +} + +extern SECStatus +CERT_SetOCSPFailureMode(SEC_OcspFailureMode ocspFailureMode) +{ + switch (ocspFailureMode) { + case ocspMode_FailureIsVerificationFailure: + case ocspMode_FailureIsNotAVerificationFailure: + break; + default: + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + PR_EnterMonitor(OCSP_Global.monitor); + OCSP_Global.ocspFailureMode = ocspFailureMode; + PR_ExitMonitor(OCSP_Global.monitor); + return SECSuccess; +} + +SECStatus +CERT_OCSPCacheSettings(PRInt32 maxCacheEntries, + PRUint32 minimumSecondsToNextFetchAttempt, + PRUint32 maximumSecondsToNextFetchAttempt) +{ + if (minimumSecondsToNextFetchAttempt > maximumSecondsToNextFetchAttempt + || maxCacheEntries < -1) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + PR_EnterMonitor(OCSP_Global.monitor); + + if (maxCacheEntries < 0) { + OCSP_Global.maxCacheEntries = -1; /* disable cache */ + } else if (maxCacheEntries == 0) { + OCSP_Global.maxCacheEntries = 0; /* unlimited cache entries */ + } else { + OCSP_Global.maxCacheEntries = maxCacheEntries; + } + + if (minimumSecondsToNextFetchAttempt < + OCSP_Global.minimumSecondsToNextFetchAttempt + || maximumSecondsToNextFetchAttempt < + OCSP_Global.maximumSecondsToNextFetchAttempt) { + /* + * Ensure our existing cache entries are not used longer than the + * new settings allow, we're lazy and just clear the cache + */ + CERT_ClearOCSPCache(); + } + + OCSP_Global.minimumSecondsToNextFetchAttempt = + minimumSecondsToNextFetchAttempt; + OCSP_Global.maximumSecondsToNextFetchAttempt = + maximumSecondsToNextFetchAttempt; + ocsp_CheckCacheSize(&OCSP_Global.cache); + + PR_ExitMonitor(OCSP_Global.monitor); + return SECSuccess; +} + +/* this function is called at NSS initialization time */ +SECStatus OCSP_InitGlobal(void) +{ + SECStatus rv = SECFailure; + + if (OCSP_Global.monitor == NULL) { + OCSP_Global.monitor = PR_NewMonitor(); + } + if (!OCSP_Global.monitor) + return SECFailure; + + PR_EnterMonitor(OCSP_Global.monitor); + if (!OCSP_Global.cache.entries) { + OCSP_Global.cache.entries = + PL_NewHashTable(0, + ocsp_CacheKeyHashFunction, + ocsp_CacheKeyCompareFunction, + PL_CompareValues, + NULL, + NULL); + OCSP_Global.ocspFailureMode = ocspMode_FailureIsVerificationFailure; + OCSP_Global.cache.numberOfEntries = 0; + OCSP_Global.cache.MRUitem = NULL; + OCSP_Global.cache.LRUitem = NULL; + } else { + /* + * NSS might call this function twice while attempting to init. + * But it's not allowed to call this again after any activity. + */ + PORT_Assert(OCSP_Global.cache.numberOfEntries == 0); + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + } + if (OCSP_Global.cache.entries) + rv = SECSuccess; + PR_ExitMonitor(OCSP_Global.monitor); + return rv; +} + +SECStatus OCSP_ShutdownCache(void) +{ + if (!OCSP_Global.monitor) + return SECSuccess; + + PR_EnterMonitor(OCSP_Global.monitor); + if (OCSP_Global.cache.entries) { + CERT_ClearOCSPCache(); + PL_HashTableDestroy(OCSP_Global.cache.entries); + OCSP_Global.cache.entries = NULL; + } + PORT_Assert(OCSP_Global.cache.numberOfEntries == 0); + OCSP_Global.cache.MRUitem = NULL; + OCSP_Global.cache.LRUitem = NULL; + PR_ExitMonitor(OCSP_Global.monitor); + return SECSuccess; } /* @@ -109,14 +900,14 @@ static const SEC_HttpClientFcn *GetRegisteredHttpClient() { const SEC_HttpClientFcn *retval; - if (!OCSP_Global.lock) { + if (!OCSP_Global.monitor) { PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return NULL; } - PR_Lock(OCSP_Global.lock); + PR_EnterMonitor(OCSP_Global.monitor); retval = OCSP_Global.defaultHttpClientFcn; - PR_Unlock(OCSP_Global.lock); + PR_ExitMonitor(OCSP_Global.monitor); return retval; } @@ -989,21 +1780,21 @@ loser: * it deserves to be mentioned. * * Any problem causes a null return and error set: - * SEC_ERROR_UNKNOWN_ISSUER + * SEC_ERROR_UNKNOWN_ISSUER * Other errors are low-level problems (no memory, bad database, etc.). */ static ocspSingleRequest ** ocsp_CreateSingleRequestList(PRArenaPool *arena, CERTCertList *certList, - int64 time, PRBool includeLocator) + int64 time, PRBool includeLocator) { ocspSingleRequest **requestList = NULL; - CERTCertListNode *node; + CERTCertListNode *node = NULL; int i, count; void *mark = PORT_ArenaMark(arena); node = CERT_LIST_HEAD(certList); for (count = 0; !CERT_LIST_END(node, certList); count++) { - node = CERT_LIST_NEXT(node); + node = CERT_LIST_NEXT(node); } if (count == 0) @@ -1016,23 +1807,23 @@ ocsp_CreateSingleRequestList(PRArenaPool *arena, CERTCertList *certList, node = CERT_LIST_HEAD(certList); for (i = 0; !CERT_LIST_END(node, certList); i++) { requestList[i] = PORT_ArenaZNew(arena, ocspSingleRequest); - if (requestList[i] == NULL) - goto loser; + if (requestList[i] == NULL) + goto loser; - requestList[i]->arena = arena; - requestList[i]->reqCert = ocsp_CreateCertID(arena, node->cert, time); - if (requestList[i]->reqCert == NULL) - goto loser; + requestList[i]->arena = arena; + requestList[i]->reqCert = ocsp_CreateCertID(arena, node->cert, time); + if (requestList[i]->reqCert == NULL) + goto loser; - if (includeLocator == PR_TRUE) { - SECStatus rv; + if (includeLocator == PR_TRUE) { + SECStatus rv; - rv = ocsp_AddServiceLocatorExtension(requestList[i], node->cert); - if (rv != SECSuccess) - goto loser; - } + rv = ocsp_AddServiceLocatorExtension(requestList[i], node->cert); + if (rv != SECSuccess) + goto loser; + } - node = CERT_LIST_NEXT(node); + node = CERT_LIST_NEXT(node); } PORT_Assert(i == count); @@ -1046,6 +1837,102 @@ loser: return NULL; } +static ocspSingleRequest ** +ocsp_CreateRequestFromCert(PRArenaPool *arena, + CERTOCSPCertID *certID, + CERTCertificate *singleCert, + int64 time, + PRBool includeLocator) +{ + ocspSingleRequest **requestList = NULL; + void *mark = PORT_ArenaMark(arena); + PORT_Assert(certID != NULL && singleCert != NULL); + + /* meaning of value 2: one entry + one end marker */ + requestList = PORT_ArenaNewArray(arena, ocspSingleRequest *, 2); + if (requestList == NULL) + goto loser; + requestList[0] = PORT_ArenaZNew(arena, ocspSingleRequest); + if (requestList[0] == NULL) + goto loser; + requestList[0]->arena = arena; + /* certID will live longer than the request */ + requestList[0]->reqCert = certID; + + if (includeLocator == PR_TRUE) { + SECStatus rv; + rv = ocsp_AddServiceLocatorExtension(requestList[0], singleCert); + if (rv != SECSuccess) + goto loser; + } + + PORT_ArenaUnmark(arena, mark); + requestList[1] = NULL; + return requestList; + +loser: + PORT_ArenaRelease(arena, mark); + return NULL; +} + +static CERTOCSPRequest * +ocsp_prepareEmptyOCSPRequest() +{ + PRArenaPool *arena = NULL; + CERTOCSPRequest *request = NULL; + ocspTBSRequest *tbsRequest = NULL; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (arena == NULL) { + goto loser; + } + request = PORT_ArenaZNew(arena, CERTOCSPRequest); + if (request == NULL) { + goto loser; + } + request->arena = arena; + + tbsRequest = PORT_ArenaZNew(arena, ocspTBSRequest); + if (tbsRequest == NULL) { + goto loser; + } + request->tbsRequest = tbsRequest; + /* version 1 is the default, so we need not fill in a version number */ + return request; + +loser: + if (arena != NULL) { + PORT_FreeArena(arena, PR_FALSE); + } + return NULL; +} + +static CERTOCSPRequest * +cert_CreateSingleCertOCSPRequest(CERTOCSPCertID *certID, + CERTCertificate *singleCert, + int64 time, + PRBool addServiceLocator) +{ + CERTOCSPRequest *request; + request = ocsp_prepareEmptyOCSPRequest(); + if (!request) + return NULL; + /* + * Version 1 is the default, so we need not fill in a version number. + * Now create the list of single requests, one for each cert. + */ + request->tbsRequest->requestList = + ocsp_CreateRequestFromCert(request->arena, + certID, + singleCert, + time, + addServiceLocator); + if (request->tbsRequest->requestList == NULL) { + PORT_FreeArena(request->arena, PR_FALSE); + return NULL; + } + return request; +} /* * FUNCTION: CERT_CreateOCSPRequest @@ -1083,10 +1970,12 @@ CERT_CreateOCSPRequest(CERTCertList *certList, int64 time, PRBool addServiceLocator, CERTCertificate *signerCert) { - PRArenaPool *arena = NULL; CERTOCSPRequest *request = NULL; - ocspTBSRequest *tbsRequest = NULL; + if (!certList) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } /* * XXX This should set an error, but since it is only temporary and * since PSM will not initially provide a way to turn on signing of @@ -1099,47 +1988,26 @@ CERT_CreateOCSPRequest(CERTCertList *certList, int64 time, * field of the tbsRequest. */ if (signerCert != NULL) { - return NULL; - } - - arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); - if (arena == NULL) { - goto loser; - } - - request = PORT_ArenaZNew(arena, CERTOCSPRequest); - if (request == NULL) { - goto loser; - } - request->arena = arena; - - tbsRequest = PORT_ArenaZNew(arena, ocspTBSRequest); - if (tbsRequest == NULL) { - goto loser; + return NULL; } - request->tbsRequest = tbsRequest; - - /* version 1 is the default, so we need not fill in a version number */ - + request = ocsp_prepareEmptyOCSPRequest(); + if (!request) + return NULL; /* * Now create the list of single requests, one for each cert. */ - tbsRequest->requestList = ocsp_CreateSingleRequestList(arena, certList, - time, - addServiceLocator); - if (tbsRequest->requestList == NULL) { - goto loser; + request->tbsRequest->requestList = + ocsp_CreateSingleRequestList(request->arena, + certList, + time, + addServiceLocator); + if (request->tbsRequest->requestList == NULL) { + PORT_FreeArena(request->arena, PR_FALSE); + return NULL; } return request; - -loser: - if (arena != NULL) { - PORT_FreeArena(arena, PR_FALSE); - } - return NULL; } - /* * FUNCTION: CERT_AddOCSPAcceptableResponses * Add the AcceptableResponses extension to an OCSP Request. @@ -2453,18 +3321,30 @@ CERT_GetEncodedOCSPResponse(PRArenaPool *arena, CERTCertList *certList, CERTCertificate *signerCert, void *pwArg, CERTOCSPRequest **pRequest) { - CERTOCSPRequest *request = NULL; + CERTOCSPRequest *request; + request = CERT_CreateOCSPRequest(certList, time, addServiceLocator, + signerCert); + if (!request) + return NULL; + return ocsp_GetEncodedOCSPResponseFromRequest(arena, request, location, + time, addServiceLocator, + pwArg, pRequest); +} + +static SECItem * +ocsp_GetEncodedOCSPResponseFromRequest(PRArenaPool *arena, + CERTOCSPRequest *request, + char *location, int64 time, + PRBool addServiceLocator, + void *pwArg, + CERTOCSPRequest **pRequest) +{ SECItem *encodedRequest = NULL; SECItem *encodedResponse = NULL; PRFileDesc *sock = NULL; SECStatus rv; const SEC_HttpClientFcn *registeredHttpClient = NULL; - request = CERT_CreateOCSPRequest(certList, time, addServiceLocator, - signerCert); - if (request == NULL) - goto loser; - rv = CERT_AddOCSPAcceptableResponses(request, SEC_OID_PKIX_OCSP_BASIC_RESPONSE); if (rv != SECSuccess) @@ -2511,6 +3391,24 @@ loser: return encodedResponse; } +static SECItem * +ocsp_GetEncodedOCSPResponseForSingleCert(PRArenaPool *arena, + CERTOCSPCertID *certID, + CERTCertificate *singleCert, + char *location, int64 time, + PRBool addServiceLocator, + void *pwArg, + CERTOCSPRequest **pRequest) +{ + CERTOCSPRequest *request; + request = cert_CreateSingleCertOCSPRequest(certID, singleCert, time, + addServiceLocator); + if (!request) + return NULL; + return ocsp_GetEncodedOCSPResponseFromRequest(arena, request, location, + time, addServiceLocator, + pwArg, pRequest); +} /* Checks a certificate for the key usage extension of OCSP signer. */ static PRBool @@ -3284,6 +4182,8 @@ ocsp_VerifySingleResponse(CERTOCSPSingleResponse *single, int64 now, thisUpdate, nextUpdate, tmstamp, tmp; SECStatus rv; + OCSP_TRACE(("OCSP ocsp_VerifySingleResponse, nextUpdate: %d\n", + ((single->nextUpdate) != 0))); /* * If all the responder said was that the given cert was unknown to it, * that is a valid response. Not very interesting to us, of course, @@ -3530,35 +4430,75 @@ ocsp_CertRevokedAfter(ocspRevokedInfo *revokedInfo, int64 time) * at the specified time. */ static SECStatus -ocsp_CertHasGoodStatus(CERTOCSPSingleResponse *single, int64 time) +ocsp_CertHasGoodStatus(ocspCertStatus *status, int64 time) { - ocspCertStatus *status; SECStatus rv; - - status = single->certStatus; - switch (status->certStatusType) { - case ocspCertStatus_good: - rv = SECSuccess; - break; - case ocspCertStatus_revoked: - rv = ocsp_CertRevokedAfter(status->certStatusInfo.revokedInfo, time); - break; - case ocspCertStatus_unknown: - PORT_SetError(SEC_ERROR_OCSP_UNKNOWN_CERT); - rv = SECFailure; - break; - case ocspCertStatus_other: - default: - PORT_Assert(0); - PORT_SetError(SEC_ERROR_OCSP_MALFORMED_RESPONSE); - rv = SECFailure; - break; + case ocspCertStatus_good: + rv = SECSuccess; + break; + case ocspCertStatus_revoked: + rv = ocsp_CertRevokedAfter(status->certStatusInfo.revokedInfo, time); + break; + case ocspCertStatus_unknown: + PORT_SetError(SEC_ERROR_OCSP_UNKNOWN_CERT); + rv = SECFailure; + break; + case ocspCertStatus_other: + default: + PORT_Assert(0); + PORT_SetError(SEC_ERROR_OCSP_MALFORMED_RESPONSE); + rv = SECFailure; + break; } - return rv; } +static SECStatus +ocsp_SingleResponseCertHasGoodStatus(CERTOCSPSingleResponse *single, int64 time) +{ + return ocsp_CertHasGoodStatus(single->certStatus, time); +} + +/* return value SECFailure means: not found or not fresh */ +static SECStatus +ocsp_GetCachedOCSPResponseStatusIfFresh(CERTOCSPCertID *certID, + int64 time, + SECStatus *rv_ocsp) +{ + OCSPCacheItem *cacheItem = NULL; + SECStatus rv = SECFailure; + + if (!certID || !rv_ocsp) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + *rv_ocsp = SECFailure; + + PR_EnterMonitor(OCSP_Global.monitor); + cacheItem = ocsp_FindCacheEntry(&OCSP_Global.cache, certID); + if (cacheItem && ocsp_IsCacheItemFresh(cacheItem)) { + /* having an arena means, we have a cached certStatus */ + if (cacheItem->certStatusArena) { + *rv_ocsp = ocsp_CertHasGoodStatus(&cacheItem->certStatus, time); + rv = SECSuccess; + } else { + /* + * No status cached, the previous attempt failed. + * If OCSP is required, we never decide based on a failed attempt + * However, if OCSP is optional, a recent OCSP failure is + * an allowed good state. + */ + if (OCSP_Global.ocspFailureMode == + ocspMode_FailureIsNotAVerificationFailure) { + rv = SECSuccess; + *rv_ocsp = SECSuccess; + } + } + } + PR_ExitMonitor(OCSP_Global.monitor); + return rv; +} /* * FUNCTION: CERT_CheckOCSPStatus @@ -3614,17 +4554,70 @@ SECStatus CERT_CheckOCSPStatus(CERTCertDBHandle *handle, CERTCertificate *cert, int64 time, void *pwArg) { + CERTOCSPCertID *certID; + PRBool certIDWasConsumed = PR_FALSE; + SECStatus rv = SECFailure; + SECStatus rv_ocsp; + + OCSP_TRACE_CERT(cert); + OCSP_TRACE_TIME("## requested validity time:", time); + + certID = CERT_CreateOCSPCertID(cert, time); + if (!certID) + return SECFailure; + rv = ocsp_GetCachedOCSPResponseStatusIfFresh(certID, time, &rv_ocsp); + if (rv == SECSuccess) { + CERT_DestroyOCSPCertID(certID); + return rv_ocsp; + } + rv = ocsp_GetOCSPStatusFromNetwork(handle, certID, cert, time, pwArg, + &certIDWasConsumed, &rv_ocsp); + if (rv != SECSuccess) { + /* we were unable to obtain ocsp status */ + PR_EnterMonitor(OCSP_Global.monitor); + if (OCSP_Global.ocspFailureMode == + ocspMode_FailureIsVerificationFailure) { + rv_ocsp = SECFailure; + } else { + rv_ocsp = SECSuccess; + } + PR_ExitMonitor(OCSP_Global.monitor); + } + if (!certIDWasConsumed) { + CERT_DestroyOCSPCertID(certID); + } + return rv_ocsp; +} + +/* + * Status in *certIDWasConsumed will always be correct, regardless of + * return value. + */ +static SECStatus +ocsp_GetOCSPStatusFromNetwork(CERTCertDBHandle *handle, + CERTOCSPCertID *certID, + CERTCertificate *cert, + int64 time, + void *pwArg, + PRBool *certIDWasConsumed, + SECStatus *rv_ocsp) +{ char *location = NULL; PRBool locationIsDefault; - CERTCertList *certList = NULL; SECItem *encodedResponse = NULL; CERTOCSPRequest *request = NULL; CERTOCSPResponse *response = NULL; CERTCertificate *signerCert = NULL; CERTCertificate *issuerCert = NULL; - CERTOCSPCertID *certID; SECStatus rv = SECFailure; + CERTOCSPSingleResponse *single = NULL; + if (!certIDWasConsumed || !rv_ocsp) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + *certIDWasConsumed = PR_FALSE; + *rv_ocsp = SECFailure; /* * The first thing we need to do is find the location of the responder. @@ -3639,17 +4632,17 @@ CERT_CheckOCSPStatus(CERTCertDBHandle *handle, CERTCertificate *cert, */ location = ocsp_GetResponderLocation(handle, cert, &locationIsDefault); if (location == NULL) { - int err = PORT_GetError(); - if (err == SEC_ERROR_EXTENSION_NOT_FOUND || - err == SEC_ERROR_CERT_BAD_ACCESS_LOCATION) { - PORT_SetError(0); - return SECSuccess; - } - return SECFailure; + int err = PORT_GetError(); + if (err == SEC_ERROR_EXTENSION_NOT_FOUND || + err == SEC_ERROR_CERT_BAD_ACCESS_LOCATION) { + PORT_SetError(0); + *rv_ocsp = SECSuccess; + return SECSuccess; + } + return SECFailure; } /* - * For now, create a cert-list of one. * XXX In the fullness of time, we will want/need to handle a * certificate chain. This will be done either when a new parameter * tells us to, or some configuration variable tells us to. In any @@ -3664,31 +4657,18 @@ CERT_CheckOCSPStatus(CERTCertDBHandle *handle, CERTCertificate *cert, * if you do not understand this.) */ - certList = CERT_NewCertList(); - if (certList == NULL) - goto loser; - - /* dup it because freeing the list will destroy the cert, too */ - cert = CERT_DupCertificate(cert); - if (cert == NULL) - goto loser; - - if (CERT_AddCertToListTail(certList, cert) != SECSuccess) { - CERT_DestroyCertificate(cert); - goto loser; - } - /* * XXX If/when signing of requests is supported, that second NULL * should be changed to be the signer certificate. Not sure if that * should be passed into this function or retrieved via some operation * on the handle/context. */ - encodedResponse = CERT_GetEncodedOCSPResponse(NULL, certList, location, - time, locationIsDefault, - NULL, pwArg, &request); + encodedResponse = + ocsp_GetEncodedOCSPResponseForSingleCert(NULL, certID, cert, location, + time, locationIsDefault, + pwArg, &request); if (encodedResponse == NULL) { - goto loser; + goto loser; } response = CERT_DecodeOCSPResponse(encodedResponse); @@ -3727,20 +4707,26 @@ CERT_CheckOCSPStatus(CERTCertDBHandle *handle, CERTCertificate *cert, * Again, we are only doing one request for one cert. * XXX When we handle cert chains, the following code will obviously * have to be modified, in coordation with the code above that will - * have to determine how to make multiple requests, etc. It will need - * to loop, and for each certID in the request, find the matching - * single response and check the status specified by it. - * - * We are helped here in that we know that the requests are made with - * the request list in the same order as the order of the certs we hand - * to it. This is why I can directly access the first member of the - * single request array for the one cert I care about. + * have to determine how to make multiple requests, etc. */ - certID = request->tbsRequest->requestList[0]->reqCert; - rv = CERT_GetOCSPStatusForCertID(handle, response, certID, - signerCert, time); + rv = ocsp_GetVerifiedSingleResponseForCertID(handle, response, certID, + signerCert, time, &single); + if (rv != SECSuccess) + goto loser; + + *rv_ocsp = ocsp_SingleResponseCertHasGoodStatus(single, time); + loser: + PR_EnterMonitor(OCSP_Global.monitor); + if (OCSP_Global.maxCacheEntries >= 0) { + /* single == NULL means: remember response failure */ + ocsp_CreateOrUpdateCacheEntry(&OCSP_Global.cache, certID, single, + certIDWasConsumed); + /* ignore cache update failures */ + } + PR_ExitMonitor(OCSP_Global.monitor); + if (issuerCert != NULL) CERT_DestroyCertificate(issuerCert); if (signerCert != NULL) @@ -3751,19 +4737,19 @@ loser: CERT_DestroyOCSPRequest(request); if (encodedResponse != NULL) SECITEM_FreeItem(encodedResponse, PR_TRUE); - if (certList != NULL) - CERT_DestroyCertList(certList); if (location != NULL) PORT_Free(location); return rv; } -SECStatus -CERT_GetOCSPStatusForCertID(CERTCertDBHandle *handle, - CERTOCSPResponse *response, - CERTOCSPCertID *certID, - CERTCertificate *signerCert, - int64 time) +static SECStatus +ocsp_GetVerifiedSingleResponseForCertID(CERTCertDBHandle *handle, + CERTOCSPResponse *response, + CERTOCSPCertID *certID, + CERTCertificate *signerCert, + int64 time, + CERTOCSPSingleResponse + **pSingleResponse) { SECStatus rv; ocspResponseData *responseData; @@ -3775,8 +4761,8 @@ CERT_GetOCSPStatusForCertID(CERTCertDBHandle *handle, */ responseData = ocsp_GetResponseData(response, NULL); if (responseData == NULL) { - rv = SECFailure; - goto loser; + rv = SECFailure; + goto loser; } /* @@ -3787,30 +4773,46 @@ CERT_GetOCSPStatusForCertID(CERTCertDBHandle *handle, */ rv = DER_GeneralizedTimeToTime(&producedAt, &responseData->producedAt); if (rv != SECSuccess) - goto loser; + goto loser; single = ocsp_GetSingleResponseForCertID(responseData->responses, - handle, certID); + handle, certID); if (single == NULL) { - rv = SECFailure; - goto loser; + rv = SECFailure; + goto loser; } rv = ocsp_VerifySingleResponse(single, handle, signerCert, producedAt); if (rv != SECSuccess) - goto loser; + goto loser; + *pSingleResponse = single; + +loser: + return rv; +} + +SECStatus +CERT_GetOCSPStatusForCertID(CERTCertDBHandle *handle, + CERTOCSPResponse *response, + CERTOCSPCertID *certID, + CERTCertificate *signerCert, + int64 time) +{ + SECStatus rv; + CERTOCSPSingleResponse *single; + rv = ocsp_GetVerifiedSingleResponseForCertID(handle, response, certID, + signerCert, time, &single); + if (rv != SECSuccess) + return rv; /* * Okay, the last step is to check whether the status says revoked, * and if so how that compares to the time value passed into this routine. */ - - rv = ocsp_CertHasGoodStatus(single, time); -loser: + rv = ocsp_SingleResponseCertHasGoodStatus(single, time); return rv; } - /* * Disable status checking and destroy related structures/data. */ @@ -3883,6 +4885,9 @@ CERT_DisableOCSPChecking(CERTCertDBHandle *handle) return SECFailure; } + /* cache no longer necessary */ + CERT_ClearOCSPCache(); + /* * This is how we disable status checking. Everything else remains * in place in case we are enabled again. @@ -4087,9 +5092,12 @@ CERT_SetOCSPDefaultResponder(CERTCertDBHandle *handle, if (statusContext->defaultResponderCert != NULL) { CERT_DestroyCertificate(statusContext->defaultResponderCert); statusContext->defaultResponderCert = cert; + /*OCSP enabled, switching responder: clear cache*/ + CERT_ClearOCSPCache(); } else { PORT_Assert(statusContext->useDefaultResponder == PR_FALSE); CERT_DestroyCertificate(cert); + /*OCSP currently not enabled, no need to clear cache*/ } return SECSuccess; @@ -4196,6 +5204,9 @@ CERT_EnableOCSPDefaultResponder(CERTCertDBHandle *handle) */ statusContext->defaultResponderCert = cert; + /* we don't allow a mix of cache entries from different responders */ + CERT_ClearOCSPCache(); + /* * Finally, record the fact that we now have a default responder enabled. */ @@ -4241,6 +5252,8 @@ CERT_DisableOCSPDefaultResponder(CERTCertDBHandle *handle) if (tmpCert) { statusContext->defaultResponderCert = NULL; CERT_DestroyCertificate(tmpCert); + /* we don't allow a mix of cache entries from different responders */ + CERT_ClearOCSPCache(); } /* diff --git a/security/nss/lib/certhigh/ocsp.h b/security/nss/lib/certhigh/ocsp.h index 810bc010c..24684fbcd 100644 --- a/security/nss/lib/certhigh/ocsp.h +++ b/security/nss/lib/certhigh/ocsp.h @@ -68,6 +68,37 @@ extern SECStatus SEC_RegisterDefaultHttpClient(const SEC_HttpClientFcn *fcnTable); /* + * Sets parameters that control NSS' internal OCSP cache. + * maxCacheEntries, special varlues are: + * -1 disable cache + * 0 unlimited cache entries + * minimumSecondsToNextFetchAttempt: + * whenever an OCSP request was attempted or completed over the network, + * wait at least this number of seconds before trying to fetch again. + * maximumSecondsToNextFetchAttempt: + * this is the maximum age of a cached response we allow, until we try + * to fetch an updated response, even if the OCSP responder expects + * that newer information update will not be available yet. + */ +extern SECStatus +CERT_OCSPCacheSettings(PRInt32 maxCacheEntries, + PRUint32 minimumSecondsToNextFetchAttempt, + PRUint32 maximumSecondsToNextFetchAttempt); + +/* + * Set the desired behaviour on OCSP failures. + * See definition of ocspFailureMode for allowed choices. + */ +extern SECStatus +CERT_SetOCSPFailureMode(SEC_OcspFailureMode ocspFailureMode); + +/* + * Removes all items currently stored in the OCSP cache. + */ +extern SECStatus +CERT_ClearOCSPCache(void); + +/* * FUNCTION: CERT_EnableOCSPChecking * Turns on OCSP checking for the given certificate database. * INPUTS: diff --git a/security/nss/lib/certhigh/ocspi.h b/security/nss/lib/certhigh/ocspi.h index a1c1ccb78..279988d80 100644 --- a/security/nss/lib/certhigh/ocspi.h +++ b/security/nss/lib/certhigh/ocspi.h @@ -42,6 +42,7 @@ #ifndef _OCSPI_H_ #define _OCSPI_H_ -SECStatus InitOCSPGlobal(void); +SECStatus OCSP_InitGlobal(void); +SECStatus OCSP_ShutdownCache(void); #endif /* _OCSPI_H_ */ diff --git a/security/nss/lib/certhigh/ocspt.h b/security/nss/lib/certhigh/ocspt.h index 18ca8ecb6..19aaf98ee 100644 --- a/security/nss/lib/certhigh/ocspt.h +++ b/security/nss/lib/certhigh/ocspt.h @@ -290,4 +290,27 @@ typedef struct SEC_HttpClientFcnStruct { } fcnTable; } SEC_HttpClientFcn; +/* + * ocspMode_FailureIsVerificationFailure: + * This is the classic behaviour of NSS. + * Any OCSP failure is a verification failure (classic mode, default). + * Without a good response, OCSP networking will be retried each time + * it is required for verifying a cert. + * + * ocspMode_FailureIsNotAVerificationFailure: + * If we fail to obtain a valid OCSP response, consider the + * cert as good. + * Failed OCSP attempts might get cached and not retried until + * minimumSecondsToNextFetchAttempt. + * If we are able to obtain a valid response, the cert + * will be considered good, if either status is "good" + * or the cert was not yet revoked at verification time. + * + * Additional failure modes might be added in the future. + */ +typedef enum { + ocspMode_FailureIsVerificationFailure = 0, + ocspMode_FailureIsNotAVerificationFailure = 1 +} SEC_OcspFailureMode; + #endif /* _OCSPT_H_ */ diff --git a/security/nss/lib/nss/nss.def b/security/nss/lib/nss/nss.def index 622614da5..0495ad23d 100644 --- a/security/nss/lib/nss/nss.def +++ b/security/nss/lib/nss/nss.def @@ -887,3 +887,11 @@ SECKEY_SignatureLen; ;+ local: ;+ *; ;+}; +;+NSS_3.11.7 { +;+ global: +CERT_SetOCSPFailureMode; +CERT_OCSPCacheSettings; +CERT_ClearOCSPCache; +;+ local: +;+ *; +;+}; diff --git a/security/nss/lib/nss/nssinit.c b/security/nss/lib/nss/nssinit.c index fc36e78e1..13afd0715 100644 --- a/security/nss/lib/nss/nssinit.c +++ b/security/nss/lib/nss/nssinit.c @@ -430,7 +430,7 @@ nss_Init(const char *configdir, const char *certPrefix, const char *keyPrefix, return SECFailure; } - if (SECSuccess != InitOCSPGlobal()) { + if (SECSuccess != OCSP_InitGlobal()) { return SECFailure; } @@ -788,6 +788,7 @@ NSS_Shutdown(void) shutdownRV = SECFailure; } ShutdownCRLCache(); + OCSP_ShutdownCache(); SECOID_Shutdown(); status = STAN_Shutdown(); cert_DestroySubjectKeyIDHashTable(); |