summaryrefslogtreecommitdiff
path: root/security/nss/lib/certhigh/ocsp.c
diff options
context:
space:
mode:
Diffstat (limited to 'security/nss/lib/certhigh/ocsp.c')
-rw-r--r--security/nss/lib/certhigh/ocsp.c2272
1 files changed, 1786 insertions, 486 deletions
diff --git a/security/nss/lib/certhigh/ocsp.c b/security/nss/lib/certhigh/ocsp.c
index 9eda390b4..eb1e46e65 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,7 +67,850 @@
#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 {
+ PRMonitor *monitor;
+ const SEC_HttpClientFcn *defaultHttpClientFcn;
+ 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.monitor) {
+ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+ return SECFailure;
+ }
+
+ PR_EnterMonitor(OCSP_Global.monitor);
+ OCSP_Global.defaultHttpClientFcn = fcnTable;
+ PR_ExitMonitor(OCSP_Global.monitor);
+
+ return SECSuccess;
+}
+
+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)
+{
+ 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;
+
+ found_ocsp_item = (OCSPCacheItem *)PL_HashTableLookup(
+ cache->entries, certID);
+ if (!found_ocsp_item)
+ goto loser;
+
+ 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;
+}
+
+/*
+ * A return value of NULL means:
+ * The application did not register it's own HTTP client.
+ */
+static const SEC_HttpClientFcn *GetRegisteredHttpClient()
+{
+ const SEC_HttpClientFcn *retval;
+
+ if (!OCSP_Global.monitor) {
+ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+ return NULL;
+ }
+
+ PR_EnterMonitor(OCSP_Global.monitor);
+ retval = OCSP_Global.defaultHttpClientFcn;
+ PR_ExitMonitor(OCSP_Global.monitor);
+
+ return retval;
+}
/*
* The following structure is only used internally. It is allocated when
@@ -300,6 +1144,8 @@ const SEC_ASN1Template ocsp_PointerToResponseBytesTemplate[] = {
static const SEC_ASN1Template ocsp_BasicOCSPResponseTemplate[] = {
{ SEC_ASN1_SEQUENCE,
0, NULL, sizeof(ocspBasicOCSPResponse) },
+ { SEC_ASN1_ANY | SEC_ASN1_SAVE,
+ offsetof(ocspBasicOCSPResponse, tbsResponseDataDER) },
{ SEC_ASN1_POINTER,
offsetof(ocspBasicOCSPResponse, tbsResponseData),
ocsp_ResponseDataTemplate },
@@ -378,6 +1224,12 @@ static const SEC_ASN1Template ocsp_ResponderIDOtherTemplate[] = {
offsetof(ocspResponderID, responderIDValue.other) }
};
+/* Decode choice container, but leave x509 name object encoded */
+static const SEC_ASN1Template ocsp_ResponderIDDerNameTemplate[] = {
+ { SEC_ASN1_EXPLICIT | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 1,
+ 0, SEC_AnyTemplate }
+};
+
/*
* SingleResponse ::= SEQUENCE {
* certID CertID,
@@ -616,6 +1468,112 @@ CERT_DestroyOCSPCertID(CERTOCSPCertID* certID)
return SECFailure;
}
+/*
+ * Digest data using the specified algorithm.
+ * The necessary storage for the digest data is allocated. If "fill" is
+ * non-null, the data is put there, otherwise a SECItem is allocated.
+ * Allocation from "arena" if it is non-null, heap otherwise. Any problem
+ * results in a NULL being returned (and an appropriate error set).
+ */
+
+static SECItem *
+ocsp_DigestValue(PRArenaPool *arena, SECOidTag digestAlg,
+ SECItem *fill, const SECItem *src)
+{
+ const SECHashObject *digestObject;
+ SECItem *result = NULL;
+ void *mark = NULL;
+ void *digestBuff = NULL;
+
+ if ( arena != NULL ) {
+ mark = PORT_ArenaMark(arena);
+ }
+
+ digestObject = HASH_GetHashObjectByOidTag(digestAlg);
+ if ( digestObject == NULL ) {
+ goto loser;
+ }
+
+ if (fill == NULL || fill->data == NULL) {
+ result = SECITEM_AllocItem(arena, fill, digestObject->length);
+ if ( result == NULL ) {
+ goto loser;
+ }
+ digestBuff = result->data;
+ } else {
+ if (fill->len < digestObject->length) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ goto loser;
+ }
+ digestBuff = fill->data;
+ }
+
+ if (PK11_HashBuf(digestAlg, digestBuff,
+ src->data, src->len) != SECSuccess) {
+ goto loser;
+ }
+
+ if ( arena != NULL ) {
+ PORT_ArenaUnmark(arena, mark);
+ }
+
+ if (result == NULL) {
+ result = fill;
+ }
+ return result;
+
+loser:
+ if (arena != NULL) {
+ PORT_ArenaRelease(arena, mark);
+ } else {
+ if (result != NULL) {
+ SECITEM_FreeItem(result, (fill == NULL) ? PR_TRUE : PR_FALSE);
+ }
+ }
+ return(NULL);
+}
+
+/*
+ * Digest the cert's subject public key using the specified algorithm.
+ * The necessary storage for the digest data is allocated. If "fill" is
+ * non-null, the data is put there, otherwise a SECItem is allocated.
+ * Allocation from "arena" if it is non-null, heap otherwise. Any problem
+ * results in a NULL being returned (and an appropriate error set).
+ */
+SECItem *
+cert_GetSPKIDigest(PRArenaPool *arena, const CERTCertificate *cert,
+ SECOidTag digestAlg, SECItem *fill)
+{
+ SECItem spk;
+
+ /*
+ * Copy just the length and data pointer (nothing needs to be freed)
+ * of the subject public key so we can convert the length from bits
+ * to bytes, which is what the digest function expects.
+ */
+ spk = cert->subjectPublicKeyInfo.subjectPublicKey;
+ DER_ConvertBitString(&spk);
+
+ return ocsp_DigestValue(arena, digestAlg, fill, &spk);
+}
+
+/*
+ * Digest the cert's subject name using the specified algorithm.
+ */
+static SECItem *
+cert_GetSubjectNameDigest(PRArenaPool *arena, const CERTCertificate *cert,
+ SECOidTag digestAlg, SECItem *fill)
+{
+ SECItem name;
+
+ /*
+ * Copy just the length and data pointer (nothing needs to be freed)
+ * of the subject name
+ */
+ name = cert->derSubject;
+
+ return ocsp_DigestValue(arena, digestAlg, fill, &name);
+}
/*
* Create and fill-in a CertID. This function fills in the hash values
@@ -654,58 +1612,35 @@ ocsp_CreateCertID(PRArenaPool *arena, CERTCertificate *cert, int64 time)
goto loser;
}
- tempItem = SEC_ASN1EncodeItem(NULL, NULL, &issuerCert->subject,
- CERT_NameTemplate);
- if (tempItem == NULL) {
- goto loser;
- }
-
- if (SECITEM_AllocItem(arena, &(certID->issuerNameHash),
- SHA1_LENGTH) == NULL) {
- goto loser;
- }
- rv = PK11_HashBuf(SEC_OID_SHA1, certID->issuerNameHash.data,
- tempItem->data, tempItem->len);
- if (rv != SECSuccess) {
- goto loser;
+ if (cert_GetSubjectNameDigest(arena, issuerCert, SEC_OID_SHA1,
+ &(certID->issuerNameHash)) == NULL) {
+ goto loser;
}
certID->issuerSHA1NameHash.data = certID->issuerNameHash.data;
certID->issuerSHA1NameHash.len = certID->issuerNameHash.len;
- /* cache the other two hash algorithms as well */
- if (SECITEM_AllocItem(arena, &(certID->issuerMD5NameHash),
- MD5_LENGTH) == NULL) {
- goto loser;
- }
- rv = PK11_HashBuf(SEC_OID_MD5, certID->issuerMD5NameHash.data,
- tempItem->data, tempItem->len);
- if (rv != SECSuccess) {
- goto loser;
- }
- if (SECITEM_AllocItem(arena, &(certID->issuerMD2NameHash),
- MD2_LENGTH) == NULL) {
- goto loser;
- }
- rv = PK11_HashBuf(SEC_OID_MD2, certID->issuerMD2NameHash.data,
- tempItem->data, tempItem->len);
- if (rv != SECSuccess) {
- goto loser;
+
+ if (cert_GetSubjectNameDigest(arena, issuerCert, SEC_OID_MD5,
+ &(certID->issuerMD5NameHash)) == NULL) {
+ goto loser;
}
- SECITEM_FreeItem(tempItem, PR_TRUE);
- tempItem = NULL;
+ if (cert_GetSubjectNameDigest(arena, issuerCert, SEC_OID_MD2,
+ &(certID->issuerMD2NameHash)) == NULL) {
+ goto loser;
+ }
- if (CERT_SPKDigestValueForCert(arena, issuerCert, SEC_OID_SHA1,
+ if (cert_GetSPKIDigest(arena, issuerCert, SEC_OID_SHA1,
&(certID->issuerKeyHash)) == NULL) {
goto loser;
}
certID->issuerSHA1KeyHash.data = certID->issuerKeyHash.data;
certID->issuerSHA1KeyHash.len = certID->issuerKeyHash.len;
/* cache the other two hash algorithms as well */
- if (CERT_SPKDigestValueForCert(arena, issuerCert, SEC_OID_MD5,
+ if (cert_GetSPKIDigest(arena, issuerCert, SEC_OID_MD5,
&(certID->issuerMD5KeyHash)) == NULL) {
goto loser;
}
- if (CERT_SPKDigestValueForCert(arena, issuerCert, SEC_OID_MD2,
+ if (cert_GetSPKIDigest(arena, issuerCert, SEC_OID_MD2,
&(certID->issuerMD2KeyHash)) == NULL) {
goto loser;
}
@@ -801,6 +1736,7 @@ ocsp_AddServiceLocatorExtension(ocspSingleRequest *singleRequest,
/* prepare for following loser gotos */
rv = SECFailure;
+ PORT_SetError(0);
extensionHandle = cert_StartExtensions(singleRequest,
singleRequest->arena, SetSingleReqExts);
@@ -844,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)
@@ -871,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);
@@ -901,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
@@ -938,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
@@ -954,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.
@@ -1517,9 +2530,19 @@ loser:
* is only used internally. When this interface is officially exported,
* each assertion below will need to be followed-up with setting an error
* and returning (null).
+ *
+ * FUNCTION: ocsp_GetResponseData
+ * Returns ocspResponseData structure and a pointer to tbs response
+ * data DER from a valid ocsp response.
+ * INPUTS:
+ * CERTOCSPResponse *response
+ * structure of a valid ocsp response
+ * RETURN:
+ * decoded OCSP response data and a pointer(tbsResponseDataDER) to its
+ * undecoded data DER.
*/
static ocspResponseData *
-ocsp_GetResponseData(CERTOCSPResponse *response)
+ocsp_GetResponseData(CERTOCSPResponse *response, SECItem **tbsResponseDataDER)
{
ocspBasicOCSPResponse *basic;
ocspResponseData *responseData;
@@ -1537,6 +2560,13 @@ ocsp_GetResponseData(CERTOCSPResponse *response)
responseData = basic->tbsResponseData;
PORT_Assert(responseData != NULL);
+ if (tbsResponseDataDER) {
+ *tbsResponseDataDER = &basic->tbsResponseDataDER;
+
+ PORT_Assert((*tbsResponseDataDER)->data != NULL);
+ PORT_Assert((*tbsResponseDataDER)->len != 0);
+ }
+
return responseData;
}
@@ -1702,6 +2732,8 @@ ocsp_ParseURL(char *url, char **pHostname, PRUint16 *pPort, char **pPath)
path[len] = '\0';
} else {
path = PORT_Strdup("/");
+ if (path == NULL)
+ goto loser;
}
*pHostname = hostname;
@@ -1712,8 +2744,6 @@ ocsp_ParseURL(char *url, char **pHostname, PRUint16 *pPort, char **pPath)
loser:
if (hostname != NULL)
PORT_Free(hostname);
- if (path != NULL)
- PORT_Free(path);
PORT_SetError(SEC_ERROR_CERT_BAD_ACCESS_LOCATION);
return SECFailure;
}
@@ -2133,6 +3163,110 @@ ocsp_GetEncodedResponse(PRArenaPool *arena, PRFileDesc *sock)
return result;
}
+/*
+ * Limit the size of http responses we are willing to accept.
+ */
+#define MAX_WANTED_OCSP_RESPONSE_LEN 64*1024
+
+static SECItem *
+fetchOcspHttpClientV1(PRArenaPool *arena,
+ const SEC_HttpClientFcnV1 *hcv1,
+ char *location,
+ SECItem *encodedRequest)
+{
+ char *hostname = NULL;
+ char *path = NULL;
+ PRUint16 port;
+ SECItem *encodedResponse = NULL;
+ SEC_HTTP_SERVER_SESSION pServerSession = NULL;
+ SEC_HTTP_REQUEST_SESSION pRequestSession = NULL;
+ PRUint16 myHttpResponseCode;
+ const char *myHttpResponseData;
+ PRUint32 myHttpResponseDataLen;
+
+ if (ocsp_ParseURL(location, &hostname, &port, &path) == SECFailure) {
+ PORT_SetError(SEC_ERROR_OCSP_MALFORMED_REQUEST);
+ goto loser;
+ }
+
+ PORT_Assert(hostname != NULL);
+ PORT_Assert(path != NULL);
+
+ if ((*hcv1->createSessionFcn)(
+ hostname,
+ port,
+ &pServerSession) != SECSuccess) {
+ PORT_SetError(SEC_ERROR_OCSP_SERVER_ERROR);
+ goto loser;
+ }
+
+ /* We use a non-zero timeout, which means:
+ - the client will use blocking I/O
+ - TryFcn will not return WOULD_BLOCK nor a poll descriptor
+ - it's sufficient to call TryFcn once
+ */
+
+ if ((*hcv1->createFcn)(
+ pServerSession,
+ "http",
+ path,
+ "POST",
+ PR_TicksPerSecond() * 60,
+ &pRequestSession) != SECSuccess) {
+ PORT_SetError(SEC_ERROR_OCSP_SERVER_ERROR);
+ goto loser;
+ }
+
+ if ((*hcv1->setPostDataFcn)(
+ pRequestSession,
+ (char*)encodedRequest->data,
+ encodedRequest->len,
+ "application/ocsp-request") != SECSuccess) {
+ PORT_SetError(SEC_ERROR_OCSP_SERVER_ERROR);
+ goto loser;
+ }
+
+ /* we don't want result objects larger than this: */
+ myHttpResponseDataLen = MAX_WANTED_OCSP_RESPONSE_LEN;
+
+ if ((*hcv1->trySendAndReceiveFcn)(
+ pRequestSession,
+ NULL,
+ &myHttpResponseCode,
+ NULL,
+ NULL,
+ &myHttpResponseData,
+ &myHttpResponseDataLen) != SECSuccess) {
+ PORT_SetError(SEC_ERROR_OCSP_SERVER_ERROR);
+ goto loser;
+ }
+
+ if (myHttpResponseCode != 200) {
+ PORT_SetError(SEC_ERROR_OCSP_BAD_HTTP_RESPONSE);
+ goto loser;
+ }
+
+ encodedResponse = SECITEM_AllocItem(arena, NULL, myHttpResponseDataLen);
+
+ if (!encodedResponse) {
+ PORT_SetError(SEC_ERROR_NO_MEMORY);
+ goto loser;
+ }
+
+ PORT_Memcpy(encodedResponse->data, myHttpResponseData, myHttpResponseDataLen);
+
+loser:
+ if (pRequestSession != NULL)
+ (*hcv1->freeFcn)(pRequestSession);
+ if (pServerSession != NULL)
+ (*hcv1->freeSessionFcn)(pServerSession);
+ if (path != NULL)
+ PORT_Free(path);
+ if (hostname != NULL)
+ PORT_Free(hostname);
+
+ return encodedResponse;
+}
/*
* FUNCTION: CERT_GetEncodedOCSPResponse
@@ -2187,16 +3321,29 @@ 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;
-
- request = CERT_CreateOCSPRequest(certList, time, addServiceLocator,
- signerCert);
- if (request == NULL)
- goto loser;
+ const SEC_HttpClientFcn *registeredHttpClient = NULL;
rv = CERT_AddOCSPAcceptableResponses(request,
SEC_OID_PKIX_OCSP_BASIC_RESPONSE);
@@ -2207,11 +3354,27 @@ CERT_GetEncodedOCSPResponse(PRArenaPool *arena, CERTCertList *certList,
if (encodedRequest == NULL)
goto loser;
- sock = ocsp_SendEncodedRequest(location, encodedRequest);
- if (sock == NULL)
- goto loser;
+ registeredHttpClient = GetRegisteredHttpClient();
+
+ if (registeredHttpClient
+ &&
+ registeredHttpClient->version == 1) {
+ encodedResponse = fetchOcspHttpClientV1(
+ arena,
+ &registeredHttpClient->fcnTable.ftable1,
+ location,
+ encodedRequest);
+ }
+ else {
+ /* use internal http client */
+
+ sock = ocsp_SendEncodedRequest(location, encodedRequest);
+ if (sock == NULL)
+ goto loser;
+
+ encodedResponse = ocsp_GetEncodedResponse(arena, sock);
+ }
- encodedResponse = ocsp_GetEncodedResponse(arena, sock);
if (encodedResponse != NULL && pRequest != NULL) {
*pRequest = request;
request = NULL; /* avoid destroying below */
@@ -2228,10 +3391,28 @@ 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
-ocsp_CertIsOCSPSigner(CERTCertificate *cert)
+ocsp_CertIsOCSPDesignatedResponder(CERTCertificate *cert)
{
SECStatus rv;
SECItem extItem;
@@ -2268,6 +3449,7 @@ ocsp_CertIsOCSPSigner(CERTCertificate *cert)
loser:
retval = PR_FALSE;
+ PORT_SetError(SEC_ERROR_OCSP_INVALID_SIGNING_CERT);
goto done;
success:
retval = PR_TRUE;
@@ -2315,19 +3497,19 @@ ocsp_matchcert(SECItem *certIndex,CERTCertificate *testCert)
item.data = buf;
item.len = SHA1_LENGTH;
- if (CERT_SPKDigestValueForCert(NULL,testCert,SEC_OID_SHA1, &item) == NULL) {
+ if (cert_GetSPKIDigest(NULL,testCert,SEC_OID_SHA1, &item) == NULL) {
return PR_FALSE;
}
if (SECITEM_ItemsAreEqual(certIndex,&item)) {
return PR_TRUE;
}
- if (CERT_SPKDigestValueForCert(NULL,testCert,SEC_OID_MD5, &item) == NULL) {
+ if (cert_GetSPKIDigest(NULL,testCert,SEC_OID_MD5, &item) == NULL) {
return PR_FALSE;
}
if (SECITEM_ItemsAreEqual(certIndex,&item)) {
return PR_TRUE;
}
- if (CERT_SPKDigestValueForCert(NULL,testCert,SEC_OID_MD2, &item) == NULL) {
+ if (cert_GetSPKIDigest(NULL,testCert,SEC_OID_MD2, &item) == NULL) {
return PR_FALSE;
}
if (SECITEM_ItemsAreEqual(certIndex,&item)) {
@@ -2337,50 +3519,71 @@ ocsp_matchcert(SECItem *certIndex,CERTCertificate *testCert)
return PR_FALSE;
}
+static PRBool
+ocsp_CertIsOCSPDefaultResponder(CERTCertDBHandle *handle, CERTCertificate *cert);
+
static CERTCertificate *
ocsp_CertGetDefaultResponder(CERTCertDBHandle *handle,CERTOCSPCertID *certID);
/*
- * Check the signature on some OCSP data. This is a helper function that
- * can be used to check either a request or a response. The result is
- * saved in the signature structure itself for future reference (to avoid
- * repeating the expensive verification operation), as well as returned.
- * In addition to checking the signature, the certificate (and its chain)
- * are also checked for validity (at the specified time) and usage.
- *
- * The type of cert lookup to be performed is specified by "lookupByName":
- * if true, then "certIndex" is actually a CERTName; otherwise it is a
- * SECItem which contains a key hash.
- *
- * If the signature verifies okay, and the argument "pSignerCert" is not
- * null, that parameter will be filled-in with a pointer to the signer's
- * certificate. The caller is then responsible for destroying the cert.
- *
- * A return of SECSuccess means the verification succeeded. If not,
- * an error will be set with the reason. Most likely are:
+ * FUNCTION: CERT_VerifyOCSPResponseSignature
+ * Check the signature on an OCSP Response. Will also perform a
+ * verification of the signer's certificate. Note, however, that a
+ * successful verification does not make any statement about the
+ * signer's *authority* to provide status for the certificate(s),
+ * that must be checked individually for each certificate.
+ * INPUTS:
+ * CERTOCSPResponse *response
+ * Pointer to response structure with signature to be checked.
+ * CERTCertDBHandle *handle
+ * Pointer to CERTCertDBHandle for certificate DB to use for verification.
+ * void *pwArg
+ * Pointer to argument for password prompting, if needed.
+ * OUTPUTS:
+ * CERTCertificate **pSignerCert
+ * Pointer in which to store signer's certificate; only filled-in if
+ * non-null.
+ * RETURN:
+ * Returns SECSuccess when signature is valid, anything else means invalid.
+ * Possible errors set:
+ * SEC_ERROR_OCSP_MALFORMED_RESPONSE - unknown type of ResponderID
+ * SEC_ERROR_INVALID_TIME - bad format of "ProducedAt" time
* SEC_ERROR_UNKNOWN_SIGNER - signer's cert could not be found
* SEC_ERROR_BAD_SIGNATURE - the signature did not verify
- * Other errors are any of the many possible failures in cert verification
- * (e.g. SEC_ERROR_REVOKED_CERTIFICATE, SEC_ERROR_UNTRUSTED_ISSUER) when
- * verifying the signer's cert, or low-level problems (no memory, etc.)
+ * Other errors are any of the many possible failures in cert verification
+ * (e.g. SEC_ERROR_REVOKED_CERTIFICATE, SEC_ERROR_UNTRUSTED_ISSUER) when
+ * verifying the signer's cert, or low-level problems (no memory, etc.)
*/
-static SECStatus
-ocsp_CheckSignature(ocspSignature *signature, void *tbs,
- const SEC_ASN1Template *encodeTemplate,
- CERTCertDBHandle *handle, SECCertUsage certUsage,
- int64 checkTime, PRBool lookupByName, void *certIndex,
- void *pwArg, CERTCertificate **pSignerCert,
- CERTCertificate *issuer)
+SECStatus
+CERT_VerifyOCSPResponseSignature(CERTOCSPResponse *response,
+ CERTCertDBHandle *handle, void *pwArg,
+ CERTCertificate **pSignerCert,
+ CERTCertificate *issuer)
{
SECItem rawSignature;
- SECItem *encodedTBS = NULL;
+ SECItem *tbsResponseDataDER;
CERTCertificate *responder = NULL;
CERTCertificate *signerCert = NULL;
SECKEYPublicKey *signerKey = NULL;
CERTCertificate **certs = NULL;
SECStatus rv = SECFailure;
- int certCount;
- int i;
+ int certCount = 0;
+ PRBool lookupByName;
+ void *certIndex;
+ int64 producedAt;
+
+ /* ocsp_DecodeBasicOCSPResponse will fail if asn1 decoder is unable
+ * to properly decode tbsData (see the function and
+ * ocsp_BasicOCSPResponseTemplate). Thus, tbsData can not be
+ * equal to null */
+ ocspResponseData *tbsData = ocsp_GetResponseData(response,
+ &tbsResponseDataDER);
+ ocspSignature *signature = ocsp_GetResponseSignature(response);
+
+ if (!signature) {
+ PORT_SetError(SEC_ERROR_OCSP_BAD_SIGNATURE);
+ return SECFailure;
+ }
/*
* If this signature has already gone through verification, just
@@ -2396,6 +3599,23 @@ ocsp_CheckSignature(ocspSignature *signature, void *tbs,
return signature->status;
}
+ PORT_Assert(tbsData->responderID != NULL);
+ switch (tbsData->responderID->responderIDType) {
+ case ocspResponderID_byName:
+ lookupByName = PR_TRUE;
+ certIndex = &tbsData->derResponderID;
+ break;
+ case ocspResponderID_byKey:
+ lookupByName = PR_FALSE;
+ certIndex = &tbsData->responderID->responderIDValue.keyHash;
+ break;
+ case ocspResponderID_other:
+ default:
+ PORT_Assert(0);
+ PORT_SetError(SEC_ERROR_OCSP_MALFORMED_RESPONSE);
+ return SECFailure;
+ }
+
/*
* If the signature contains some certificates as well, temporarily
* import them in case they are needed for verification.
@@ -2403,39 +3623,47 @@ ocsp_CheckSignature(ocspSignature *signature, void *tbs,
* Note that the result of this is that each cert in "certs" needs
* to be destroyed.
*/
- certCount = 0;
if (signature->derCerts != NULL) {
for (; signature->derCerts[certCount] != NULL; certCount++) {
/* just counting */
- /*IMPORT CERT TO SPKI TABLE */
}
+ rv = CERT_ImportCerts(handle, certUsageStatusResponder, certCount,
+ signature->derCerts, &certs,
+ PR_FALSE, PR_FALSE, NULL);
+ if (rv != SECSuccess)
+ goto finish;
}
- rv = CERT_ImportCerts(handle, certUsage, certCount,
- signature->derCerts, &certs,
- PR_FALSE, PR_FALSE, NULL);
- if (rv != SECSuccess)
- goto finish;
/*
* Now look up the certificate that did the signing.
* The signer can be specified either by name or by key hash.
*/
if (lookupByName) {
- SECItem *encodedName;
-
- encodedName = SEC_ASN1EncodeItem(NULL, NULL, certIndex,
- CERT_NameTemplate);
- if (encodedName == NULL)
- goto finish;
-
- signerCert = CERT_FindCertByName(handle, encodedName);
- SECITEM_FreeItem(encodedName, PR_TRUE);
+ SECItem *crIndex = (SECItem*)certIndex;
+ SECItem encodedName;
+ PLArenaPool *arena;
+
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (arena != NULL) {
+
+ rv = SEC_QuickDERDecodeItem(arena, &encodedName,
+ ocsp_ResponderIDDerNameTemplate,
+ crIndex);
+ if (rv != SECSuccess) {
+ if (PORT_GetError() == SEC_ERROR_BAD_DER)
+ PORT_SetError(SEC_ERROR_OCSP_MALFORMED_RESPONSE);
+ } else {
+ signerCert = CERT_FindCertByName(handle, &encodedName);
+ }
+ PORT_FreeArena(arena, PR_FALSE);
+ }
} else {
/*
* The signer is either 1) a known issuer CA we passed in,
* 2) the default OCSP responder, or 3) an intermediate CA
* passed in the cert list to use. Figure out which it is.
*/
+ int i;
responder = ocsp_CertGetDefaultResponder(handle,NULL);
if (responder && ocsp_matchcert(certIndex,responder)) {
signerCert = CERT_DupCertificate(responder);
@@ -2453,7 +3681,7 @@ ocsp_CheckSignature(ocspSignature *signature, void *tbs,
rv = SECFailure;
if (PORT_GetError() == SEC_ERROR_UNKNOWN_CERT) {
/* Make the error a little more specific. */
- PORT_SetError(SEC_ERROR_UNKNOWN_SIGNER);
+ PORT_SetError(SEC_ERROR_OCSP_INVALID_SIGNING_CERT);
}
goto finish;
}
@@ -2467,14 +3695,35 @@ ocsp_CheckSignature(ocspSignature *signature, void *tbs,
signature->wasChecked = PR_TRUE;
/*
+ * The function will also verify the signer certificate; we
+ * need to tell it *when* that certificate must be valid -- for our
+ * purposes we expect it to be valid when the response was signed.
+ * The value of "producedAt" is the signing time.
+ */
+ rv = DER_GeneralizedTimeToTime(&producedAt, &tbsData->producedAt);
+ if (rv != SECSuccess)
+ goto finish;
+
+ /*
* Just because we have a cert does not mean it is any good; check
* it for validity, trust and usage.
*/
- rv = CERT_VerifyCert(handle, signerCert, PR_TRUE, certUsage, checkTime,
- pwArg, NULL);
- if (rv != SECSuccess) {
- PORT_SetError(SEC_ERROR_OCSP_INVALID_SIGNING_CERT);
- goto finish;
+ if (ocsp_CertIsOCSPDefaultResponder(handle, signerCert)) {
+ rv = SECSuccess;
+ } else {
+ if (CERT_IsCACert(signerCert, NULL)) {
+ rv = CERT_VerifyCert(handle, signerCert, PR_TRUE,
+ certUsageVerifyCA,
+ producedAt, pwArg, NULL);
+ } else {
+ rv = CERT_VerifyCert(handle, signerCert, PR_TRUE,
+ certUsageStatusResponder,
+ producedAt, pwArg, NULL);
+ }
+ if (rv != SECSuccess) {
+ PORT_SetError(SEC_ERROR_OCSP_INVALID_SIGNING_CERT);
+ goto finish;
+ }
}
/*
@@ -2484,14 +3733,6 @@ ocsp_CheckSignature(ocspSignature *signature, void *tbs,
signerKey = CERT_ExtractPublicKey(signerCert);
if (signerKey == NULL)
goto finish;
-
- /*
- * Prepare the data to be verified; it needs to be DER encoded first.
- */
- encodedTBS = SEC_ASN1EncodeItem(NULL, NULL, tbs, encodeTemplate);
- if (encodedTBS == NULL)
- goto finish;
-
/*
* We copy the signature data *pointer* and length, so that we can
* modify the length without damaging the original copy. This is a
@@ -2504,10 +3745,14 @@ ocsp_CheckSignature(ocspSignature *signature, void *tbs,
*/
DER_ConvertBitString(&rawSignature);
- rv = VFY_VerifyData(encodedTBS->data, encodedTBS->len, signerKey,
- &rawSignature,
- SECOID_GetAlgorithmTag(&signature->signatureAlgorithm),
- pwArg);
+ rv = VFY_VerifyDataWithAlgorithmID(tbsResponseDataDER->data,
+ tbsResponseDataDER->len,
+ signerKey, &rawSignature,
+ &signature->signatureAlgorithm,
+ NULL, pwArg);
+ if (rv != SECSuccess && PORT_GetError() == SEC_ERROR_BAD_SIGNATURE) {
+ PORT_SetError(SEC_ERROR_OCSP_BAD_SIGNATURE);
+ }
finish:
if (signature->wasChecked)
@@ -2531,9 +3776,6 @@ finish:
}
}
- if (encodedTBS != NULL)
- SECITEM_FreeItem(encodedTBS, PR_TRUE);
-
if (signerKey != NULL)
SECKEY_DestroyPublicKey(signerKey);
@@ -2544,93 +3786,17 @@ finish:
return rv;
}
-
-/*
- * FUNCTION: CERT_VerifyOCSPResponseSignature
- * Check the signature on an OCSP Response. Will also perform a
- * verification of the signer's certificate. Note, however, that a
- * successful verification does not make any statement about the
- * signer's *authority* to provide status for the certificate(s),
- * that must be checked individually for each certificate.
- * INPUTS:
- * CERTOCSPResponse *response
- * Pointer to response structure with signature to be checked.
- * CERTCertDBHandle *handle
- * Pointer to CERTCertDBHandle for certificate DB to use for verification.
- * void *pwArg
- * Pointer to argument for password prompting, if needed.
- * OUTPUTS:
- * CERTCertificate **pSignerCert
- * Pointer in which to store signer's certificate; only filled-in if
- * non-null.
- * RETURN:
- * Returns SECSuccess when signature is valid, anything else means invalid.
- * Possible errors set:
- * SEC_ERROR_OCSP_MALFORMED_RESPONSE - unknown type of ResponderID
- * SEC_ERROR_INVALID_TIME - bad format of "ProducedAt" time
- * SEC_ERROR_UNKNOWN_SIGNER - signer's cert could not be found
- * SEC_ERROR_BAD_SIGNATURE - the signature did not verify
- * Other errors are any of the many possible failures in cert verification
- * (e.g. SEC_ERROR_REVOKED_CERTIFICATE, SEC_ERROR_UNTRUSTED_ISSUER) when
- * verifying the signer's cert, or low-level problems (no memory, etc.)
- */
-SECStatus
-CERT_VerifyOCSPResponseSignature(CERTOCSPResponse *response,
- CERTCertDBHandle *handle, void *pwArg,
- CERTCertificate **pSignerCert,
- CERTCertificate *issuer)
-{
- ocspResponseData *tbsData; /* this is what is signed */
- PRBool byName;
- void *certIndex;
- int64 producedAt;
- SECStatus rv;
-
- tbsData = ocsp_GetResponseData(response);
-
- PORT_Assert(tbsData->responderID != NULL);
- switch (tbsData->responderID->responderIDType) {
- case ocspResponderID_byName:
- byName = PR_TRUE;
- certIndex = &tbsData->responderID->responderIDValue.name;
- break;
- case ocspResponderID_byKey:
- byName = PR_FALSE;
- certIndex = &tbsData->responderID->responderIDValue.keyHash;
- break;
- case ocspResponderID_other:
- default:
- PORT_Assert(0);
- PORT_SetError(SEC_ERROR_OCSP_MALFORMED_RESPONSE);
- return SECFailure;
- }
-
- /*
- * ocsp_CheckSignature will also verify the signer certificate; we
- * need to tell it *when* that certificate must be valid -- for our
- * purposes we expect it to be valid when the response was signed.
- * The value of "producedAt" is the signing time.
- */
- rv = DER_GeneralizedTimeToTime(&producedAt, &tbsData->producedAt);
- if (rv != SECSuccess)
- return rv;
-
- return ocsp_CheckSignature(ocsp_GetResponseSignature(response),
- tbsData, ocsp_ResponseDataTemplate,
- handle, certUsageStatusResponder, producedAt,
- byName, certIndex, pwArg, pSignerCert, issuer);
-}
-
/*
- * See if two certIDs match. This can be easy or difficult, depending
- * on whether the same hash algorithm was used.
+ * See if the request's certID and the single response's certID match.
+ * This can be easy or difficult, depending on whether the same hash
+ * algorithm was used.
*/
static PRBool
ocsp_CertIDsMatch(CERTCertDBHandle *handle,
- CERTOCSPCertID *certID1, CERTOCSPCertID *certID2)
+ CERTOCSPCertID *requestCertID,
+ CERTOCSPCertID *responseCertID)
{
PRBool match = PR_FALSE;
- SECItem *foundHash = NULL;
SECOidTag hashAlg;
SECItem *keyHash = NULL;
SECItem *nameHash = NULL;
@@ -2641,52 +3807,57 @@ ocsp_CertIDsMatch(CERTCertDBHandle *handle,
*
* We just compare the easier things first.
*/
- if (SECITEM_CompareItem(&certID1->serialNumber,
- &certID2->serialNumber) != SECEqual) {
+ if (SECITEM_CompareItem(&requestCertID->serialNumber,
+ &responseCertID->serialNumber) != SECEqual) {
goto done;
}
- if (SECOID_CompareAlgorithmID(&certID1->hashAlgorithm,
- &certID2->hashAlgorithm) == SECEqual) {
+ /*
+ * Make sure the "parameters" are not too bogus. Since we encoded
+ * requestCertID->hashAlgorithm, we don't need to check it.
+ */
+ if (responseCertID->hashAlgorithm.parameters.len > 2) {
+ goto done;
+ }
+ if (SECITEM_CompareItem(&requestCertID->hashAlgorithm.algorithm,
+ &responseCertID->hashAlgorithm.algorithm) == SECEqual) {
/*
* If the hash algorithms match then we can do a simple compare
* of the hash values themselves.
*/
- if ((SECITEM_CompareItem(&certID1->issuerNameHash,
- &certID2->issuerNameHash) == SECEqual)
- && (SECITEM_CompareItem(&certID1->issuerKeyHash,
- &certID2->issuerKeyHash) == SECEqual)) {
+ if ((SECITEM_CompareItem(&requestCertID->issuerNameHash,
+ &responseCertID->issuerNameHash) == SECEqual)
+ && (SECITEM_CompareItem(&requestCertID->issuerKeyHash,
+ &responseCertID->issuerKeyHash) == SECEqual)) {
match = PR_TRUE;
}
goto done;
}
- hashAlg = SECOID_FindOIDTag(&certID2->hashAlgorithm.algorithm);
+ hashAlg = SECOID_FindOIDTag(&responseCertID->hashAlgorithm.algorithm);
switch (hashAlg) {
case SEC_OID_SHA1:
- keyHash = &certID1->issuerSHA1KeyHash;
- nameHash = &certID1->issuerSHA1NameHash;
+ keyHash = &requestCertID->issuerSHA1KeyHash;
+ nameHash = &requestCertID->issuerSHA1NameHash;
break;
case SEC_OID_MD5:
- keyHash = &certID1->issuerMD5KeyHash;
- nameHash = &certID1->issuerMD5NameHash;
+ keyHash = &requestCertID->issuerMD5KeyHash;
+ nameHash = &requestCertID->issuerMD5NameHash;
break;
case SEC_OID_MD2:
- keyHash = &certID1->issuerMD2KeyHash;
- nameHash = &certID1->issuerMD2NameHash;
+ keyHash = &requestCertID->issuerMD2KeyHash;
+ nameHash = &requestCertID->issuerMD2NameHash;
break;
default:
- foundHash = NULL;
- break;
+ PORT_SetError(SEC_ERROR_INVALID_ALGORITHM);
+ return SECFailure;
}
- if (foundHash == NULL) {
- goto done;
- }
- PORT_Assert(keyHash && nameHash);
-
- if ((SECITEM_CompareItem(nameHash, &certID2->issuerNameHash) == SECEqual)
- && (SECITEM_CompareItem(keyHash, &certID2->issuerKeyHash) == SECEqual)) {
+ if ((keyHash != NULL)
+ && (SECITEM_CompareItem(nameHash,
+ &responseCertID->issuerNameHash) == SECEqual)
+ && (SECITEM_CompareItem(keyHash,
+ &responseCertID->issuerKeyHash) == SECEqual)) {
match = PR_TRUE;
}
@@ -2713,7 +3884,7 @@ ocsp_GetSingleResponseForCertID(CERTOCSPSingleResponse **responses,
for (i = 0; responses[i] != NULL; i++) {
single = responses[i];
- if (ocsp_CertIDsMatch(handle, certID, single->certID) == PR_TRUE) {
+ if (ocsp_CertIDsMatch(handle, certID, single->certID)) {
return single;
}
}
@@ -2754,12 +3925,13 @@ ocsp_GetCheckingContext(CERTCertDBHandle *handle)
return ocspcx;
}
+
/*
- * Return true if the given signerCert is the default responder for
- * the given certID. If not, or if any error, return false.
+ * Return cert reference if the given signerCert is the default responder for
+ * the given certID. If not, or if any error, return NULL.
*/
static CERTCertificate *
-ocsp_CertGetDefaultResponder(CERTCertDBHandle *handle,CERTOCSPCertID *certID)
+ocsp_CertGetDefaultResponder(CERTCertDBHandle *handle, CERTOCSPCertID *certID)
{
ocspCheckingContext *ocspcx;
@@ -2785,18 +3957,32 @@ loser:
}
/*
- * Return true if the given signerCert is the default responder for
- * the given certID. If not, or if any error, return false.
+ * Return true if the cert is one of the default responders configured for
+ * ocsp context. If not, or if any error, return false.
*/
static PRBool
-ocsp_CertIsDefaultResponderForCertID(CERTCertDBHandle *handle,
- CERTCertificate *signerCert,
- CERTOCSPCertID *certID)
+ocsp_CertIsOCSPDefaultResponder(CERTCertDBHandle *handle, CERTCertificate *cert)
{
- CERTCertificate *defaultResponderCert;
+ ocspCheckingContext *ocspcx;
- defaultResponderCert = ocsp_CertGetDefaultResponder(handle, certID);
- return (PRBool) (defaultResponderCert == signerCert);
+ ocspcx = ocsp_GetCheckingContext(handle);
+ if (ocspcx == NULL)
+ return PR_FALSE;
+
+ /*
+ * Right now we have only one default responder. It applies to
+ * all certs when it is used, so the check is simple and certID
+ * has no bearing on the answer. Someday in the future we may
+ * allow configuration of different responders for different
+ * issuers, and then we would have to use the issuer specified
+ * in certID to determine if signerCert is the right one.
+ */
+ if (ocspcx->useDefaultResponder &&
+ CERT_CompareCerts(ocspcx->defaultResponderCert, cert)) {
+ return PR_TRUE;
+ }
+
+ return PR_FALSE;
}
/*
@@ -2822,67 +4008,109 @@ ocsp_AuthorizedResponderForCertID(CERTCertDBHandle *handle,
CERTOCSPCertID *certID,
int64 thisUpdate)
{
- CERTCertificate *issuerCert = NULL;
- SECItem *issuerKeyHash = NULL;
+ CERTCertificate *issuerCert = NULL, *defRespCert;
+ SECItem *keyHash = NULL;
+ SECItem *nameHash = NULL;
SECOidTag hashAlg;
- PRBool okay = PR_FALSE;
+ PRBool keyHashEQ = PR_FALSE, nameHashEQ = PR_FALSE;
/*
* Check first for a trusted responder, which overrides everything else.
*/
- if (ocsp_CertIsDefaultResponderForCertID(handle, signerCert, certID))
- return PR_TRUE;
+ if ((defRespCert = ocsp_CertGetDefaultResponder(handle, certID)) &&
+ CERT_CompareCerts(defRespCert, signerCert)) {
+ return PR_TRUE;
+ }
/*
* In the other two cases, we need to do an issuer comparison.
* How we do it depends on whether the signer certificate has the
* special extension (for a designated responder) or not.
+ *
+ * First, lets check if signer of the response is the actual issuer
+ * of the cert. For that we will use signer cert key hash and cert subj
+ * name hash and will compare them with already calculated issuer key
+ * hash and issuer name hash. The hash algorithm is picked from response
+ * certID hash to avoid second hash calculation.
*/
- if (ocsp_CertIsOCSPSigner(signerCert)) {
- /*
- * The signer is a designated responder. Its issuer must match
- * the issuer of the cert being checked.
- */
- issuerCert = CERT_FindCertIssuer(signerCert, thisUpdate,
- certUsageAnyCA);
- if (issuerCert == NULL) {
- /*
- * We could leave the SEC_ERROR_UNKNOWN_ISSUER error alone,
- * but the following will give slightly more information.
- * Once we have an error stack, things will be much better.
- */
- PORT_SetError(SEC_ERROR_OCSP_UNAUTHORIZED_RESPONSE);
- goto loser;
- }
- } else {
- /*
- * The signer must *be* the issuer of the cert being checked.
- */
- issuerCert = signerCert;
+ hashAlg = SECOID_FindOIDTag(&certID->hashAlgorithm.algorithm);
+
+ keyHash = cert_GetSPKIDigest(NULL, signerCert, hashAlg, NULL);
+ if (keyHash != NULL) {
+
+ keyHashEQ =
+ (SECITEM_CompareItem(keyHash,
+ &certID->issuerKeyHash) == SECEqual);
+ SECITEM_FreeItem(keyHash, PR_TRUE);
+ }
+ if (keyHashEQ &&
+ (nameHash = cert_GetSubjectNameDigest(NULL, signerCert,
+ hashAlg, NULL))) {
+ nameHashEQ =
+ (SECITEM_CompareItem(nameHash,
+ &certID->issuerNameHash) == SECEqual);
+
+ SECITEM_FreeItem(nameHash, PR_TRUE);
+ if (nameHashEQ) {
+ /* The issuer of the cert is the the signer of the response */
+ return PR_TRUE;
+ }
}
- hashAlg = SECOID_FindOIDTag(&certID->hashAlgorithm.algorithm);
- issuerKeyHash = CERT_SPKDigestValueForCert(NULL, issuerCert, hashAlg, NULL);
- if (issuerKeyHash == NULL)
- goto loser;
- if (SECITEM_CompareItem(issuerKeyHash,
- &certID->issuerKeyHash) != SECEqual) {
- PORT_SetError(SEC_ERROR_OCSP_UNAUTHORIZED_RESPONSE);
- goto loser;
+ keyHashEQ = PR_FALSE;
+ nameHashEQ = PR_FALSE;
+
+ if (!ocsp_CertIsOCSPDesignatedResponder(signerCert)) {
+ PORT_SetError(SEC_ERROR_OCSP_UNAUTHORIZED_RESPONSE);
+ return PR_FALSE;
}
- okay = PR_TRUE;
+ /*
+ * The signer is a designated responder. Its issuer must match
+ * the issuer of the cert being checked.
+ */
+ issuerCert = CERT_FindCertIssuer(signerCert, thisUpdate,
+ certUsageAnyCA);
+ if (issuerCert == NULL) {
+ /*
+ * We could leave the SEC_ERROR_UNKNOWN_ISSUER error alone,
+ * but the following will give slightly more information.
+ * Once we have an error stack, things will be much better.
+ */
+ PORT_SetError(SEC_ERROR_OCSP_UNAUTHORIZED_RESPONSE);
+ return PR_FALSE;
+ }
-loser:
- if (issuerKeyHash != NULL)
- SECITEM_FreeItem(issuerKeyHash, PR_TRUE);
+ keyHash = cert_GetSPKIDigest(NULL, issuerCert, hashAlg, NULL);
+ nameHash = cert_GetSubjectNameDigest(NULL, issuerCert, hashAlg, NULL);
- if (issuerCert != NULL && issuerCert != signerCert)
- CERT_DestroyCertificate(issuerCert);
+ CERT_DestroyCertificate(issuerCert);
+
+ if (keyHash != NULL && nameHash != NULL) {
+ keyHashEQ =
+ (SECITEM_CompareItem(keyHash,
+ &certID->issuerKeyHash) == SECEqual);
+
+ nameHashEQ =
+ (SECITEM_CompareItem(nameHash,
+ &certID->issuerNameHash) == SECEqual);
+ }
- return okay;
+ if (keyHash) {
+ SECITEM_FreeItem(keyHash, PR_TRUE);
+ }
+ if (nameHash) {
+ SECITEM_FreeItem(nameHash, PR_TRUE);
+ }
+
+ if (keyHashEQ && nameHashEQ) {
+ return PR_TRUE;
+ }
+
+ PORT_SetError(SEC_ERROR_OCSP_UNAUTHORIZED_RESPONSE);
+ return PR_FALSE;
}
/*
@@ -2954,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,
@@ -3025,7 +4255,7 @@ ocsp_VerifySingleResponse(CERTOCSPSingleResponse *single,
* char *
* A copy of the URI for the OCSP method, if found. If either the
* extension is not present or it does not contain an entry for OCSP,
- * SEC_ERROR_EXTENSION_NOT_FOUND will be set and a NULL returned.
+ * SEC_ERROR_CERT_BAD_ACCESS_LOCATION will be set and a NULL returned.
* Any other error will also result in a NULL being returned.
*
* This result should be freed (via PORT_Free) when no longer in use.
@@ -3053,8 +4283,10 @@ CERT_GetOCSPAuthorityInfoAccessLocation(CERTCertificate *cert)
rv = CERT_FindCertExtension(cert, SEC_OID_X509_AUTH_INFO_ACCESS,
encodedAuthInfoAccess);
- if (rv == SECFailure)
+ if (rv == SECFailure) {
+ PORT_SetError(SEC_ERROR_CERT_BAD_ACCESS_LOCATION);
goto loser;
+ }
/*
* The rest of the things allocated in the routine will come out of
@@ -3084,7 +4316,7 @@ CERT_GetOCSPAuthorityInfoAccessLocation(CERTCertificate *cert)
* not there at all.
*/
if (locname == NULL) {
- PORT_SetError(SEC_ERROR_EXTENSION_NOT_FOUND);
+ PORT_SetError(SEC_ERROR_CERT_BAD_ACCESS_LOCATION);
goto loser;
}
@@ -3101,7 +4333,7 @@ CERT_GetOCSPAuthorityInfoAccessLocation(CERTCertificate *cert)
* this should probably be something more like the extension was
* badly formed.
*/
- PORT_SetError(SEC_ERROR_EXTENSION_NOT_FOUND);
+ PORT_SetError(SEC_ERROR_CERT_BAD_ACCESS_LOCATION);
goto loser;
}
@@ -3198,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
@@ -3282,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.
@@ -3307,14 +4632,17 @@ CERT_CheckOCSPStatus(CERTCertDBHandle *handle, CERTCertificate *cert,
*/
location = ocsp_GetResponderLocation(handle, cert, &locationIsDefault);
if (location == NULL) {
- if (PORT_GetError() == SEC_ERROR_EXTENSION_NOT_FOUND)
- return SECSuccess;
- else
- 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
@@ -3329,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);
@@ -3392,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)
@@ -3416,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;
@@ -3438,10 +4759,10 @@ CERT_GetOCSPStatusForCertID(CERTCertDBHandle *handle,
/*
* The ResponseData part is the real guts of the response.
*/
- responseData = ocsp_GetResponseData(response);
+ responseData = ocsp_GetResponseData(response, NULL);
if (responseData == NULL) {
- rv = SECFailure;
- goto loser;
+ rv = SECFailure;
+ goto loser;
}
/*
@@ -3452,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.
*/
@@ -3548,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.
@@ -3590,8 +4930,6 @@ ocsp_InitStatusChecking(CERTCertDBHandle *handle)
return SECSuccess;
loser:
- if (statusContext != NULL)
- PORT_Free(statusContext);
if (statusConfig != NULL)
PORT_Free(statusConfig);
return SECFailure;
@@ -3754,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;
@@ -3793,6 +5134,8 @@ CERT_EnableOCSPDefaultResponder(CERTCertDBHandle *handle)
{
ocspCheckingContext *statusContext;
CERTCertificate *cert;
+ SECStatus rv;
+ SECCertificateUsage usage;
if (handle == NULL) {
PORT_SetError(SEC_ERROR_INVALID_ARGS);
@@ -3837,11 +5180,33 @@ CERT_EnableOCSPDefaultResponder(CERTCertDBHandle *handle)
if (cert == NULL)
return SECFailure;
+ /*
+ * Supplied cert should at least have a signing capability in order for us
+ * to use it as a trusted responder cert. Ability to sign is guarantied if
+ * cert is validated to have any set of the usages below.
+ */
+ rv = CERT_VerifyCertificateNow(handle, cert, PR_TRUE,
+ certificateUsageCheckAllUsages,
+ NULL, &usage);
+ if (rv != SECSuccess || (usage & (certificateUsageSSLClient |
+ certificateUsageSSLServer |
+ certificateUsageSSLServerWithStepUp |
+ certificateUsageEmailSigner |
+ certificateUsageObjectSigner |
+ certificateUsageStatusResponder |
+ certificateUsageSSLCA)) == 0) {
+ PORT_SetError(SEC_ERROR_OCSP_RESPONDER_CERT_INVALID);
+ return SECFailure;
+ }
+
/*
* And hang onto it.
*/
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.
*/
@@ -3867,6 +5232,7 @@ CERT_DisableOCSPDefaultResponder(CERTCertDBHandle *handle)
{
CERTStatusConfig *statusConfig;
ocspCheckingContext *statusContext;
+ CERTCertificate *tmpCert;
if (handle == NULL) {
PORT_SetError(SEC_ERROR_INVALID_ARGS);
@@ -3882,9 +5248,12 @@ CERT_DisableOCSPDefaultResponder(CERTCertDBHandle *handle)
if (statusContext == NULL)
return SECFailure;
- if (statusContext->defaultResponderCert != NULL) {
- CERT_DestroyCertificate(statusContext->defaultResponderCert);
+ tmpCert = statusContext->defaultResponderCert;
+ if (tmpCert) {
statusContext->defaultResponderCert = NULL;
+ CERT_DestroyCertificate(tmpCert);
+ /* we don't allow a mix of cache entries from different responders */
+ CERT_ClearOCSPCache();
}
/*
@@ -3894,75 +5263,6 @@ CERT_DisableOCSPDefaultResponder(CERTCertDBHandle *handle)
return SECSuccess;
}
-/*
- * Digest the cert's subject public key using the specified algorithm.
- * The necessary storage for the digest data is allocated. If "fill" is
- * non-null, the data is put there, otherwise a SECItem is allocated.
- * Allocation from "arena" if it is non-null, heap otherwise. Any problem
- * results in a NULL being returned (and an appropriate error set).
- */
-SECItem *
-CERT_SPKDigestValueForCert(PRArenaPool *arena, CERTCertificate *cert,
- SECOidTag digestAlg, SECItem *fill)
-{
- const SECHashObject *digestObject;
- void *digestContext;
- SECItem *result = NULL;
- void *mark = NULL;
- SECItem spk;
-
- if ( arena != NULL ) {
- mark = PORT_ArenaMark(arena);
- }
-
- digestObject = HASH_GetHashObjectByOidTag(digestAlg);
- if ( digestObject == NULL ) {
- goto loser;
- }
-
- if ((fill == NULL) || (fill->data == NULL)) {
- result = SECITEM_AllocItem(arena, fill, digestObject->length);
- if ( result == NULL ) {
- goto loser;
- }
- fill = result;
- }
-
- /*
- * Copy just the length and data pointer (nothing needs to be freed)
- * of the subject public key so we can convert the length from bits
- * to bytes, which is what the digest function expects.
- */
- spk = cert->subjectPublicKeyInfo.subjectPublicKey;
- DER_ConvertBitString(&spk);
-
- /*
- * Now digest the value, using the specified algorithm.
- */
- digestContext = digestObject->create();
- if ( digestContext == NULL ) {
- goto loser;
- }
- digestObject->begin(digestContext);
- digestObject->update(digestContext, spk.data, spk.len);
- digestObject->end(digestContext, fill->data, &(fill->len), fill->len);
- digestObject->destroy(digestContext, PR_TRUE);
-
- if ( arena != NULL ) {
- PORT_ArenaUnmark(arena, mark);
- }
- return(fill);
-
-loser:
- if ( arena != NULL ) {
- PORT_ArenaRelease(arena, mark);
- } else {
- if ( result != NULL ) {
- SECITEM_FreeItem(result, (fill == NULL) ? PR_TRUE : PR_FALSE);
- }
- }
- return(NULL);
-}
SECStatus
CERT_GetOCSPResponseStatus(CERTOCSPResponse *response)