summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorkaie%kuix.de <devnull@localhost>2007-04-25 01:05:07 +0000
committerkaie%kuix.de <devnull@localhost>2007-04-25 01:05:07 +0000
commit286e08474b3d3e189463d8d6e78c32469447961e (patch)
tree4c1984acfbd9a78a04bda10fcb68949371ceb311
parent45105225a2252b61b132b02d8a370a88f557f16a (diff)
downloadnss-hg-286e08474b3d3e189463d8d6e78c32469447961e.tar.gz
Bug 205406, Need a local OCSP cache
r=nelson, r=rrelyea
-rw-r--r--security/nss/lib/certhigh/ocsp.c1319
-rw-r--r--security/nss/lib/certhigh/ocsp.h31
-rw-r--r--security/nss/lib/certhigh/ocspi.h3
-rw-r--r--security/nss/lib/certhigh/ocspt.h23
-rw-r--r--security/nss/lib/nss/nss.def8
-rw-r--r--security/nss/lib/nss/nssinit.c3
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();