summaryrefslogtreecommitdiffstats
path: root/security/nss/lib/certhigh/ocsp.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /security/nss/lib/certhigh/ocsp.c
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'security/nss/lib/certhigh/ocsp.c')
-rw-r--r--security/nss/lib/certhigh/ocsp.c6119
1 files changed, 6119 insertions, 0 deletions
diff --git a/security/nss/lib/certhigh/ocsp.c b/security/nss/lib/certhigh/ocsp.c
new file mode 100644
index 0000000000..4fccbc9012
--- /dev/null
+++ b/security/nss/lib/certhigh/ocsp.c
@@ -0,0 +1,6119 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Implementation of OCSP services, for both client and server.
+ * (XXX, really, mostly just for client right now, but intended to do both.)
+ */
+
+#include "prerror.h"
+#include "prprf.h"
+#include "plarena.h"
+#include "prnetdb.h"
+
+#include "seccomon.h"
+#include "secitem.h"
+#include "secoidt.h"
+#include "secasn1.h"
+#include "secder.h"
+#include "cert.h"
+#include "certi.h"
+#include "xconst.h"
+#include "secerr.h"
+#include "secoid.h"
+#include "hasht.h"
+#include "sechash.h"
+#include "secasn1.h"
+#include "plbase64.h"
+#include "keyhi.h"
+#include "cryptohi.h"
+#include "ocsp.h"
+#include "ocspti.h"
+#include "ocspi.h"
+#include "genname.h"
+#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 DEFAULT_OSCP_TIMEOUT_SECONDS 60
+#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 */
+ PLArenaPool *certStatusArena; /* NULL means: no cert status cached */
+ ocspCertStatus certStatus;
+
+ /* This may contain an error code when no OCSP response is available. */
+ SECErrorCodes missingResponseError;
+
+ 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;
+ PRUint32 timeoutSeconds;
+ OCSPCacheData cache;
+ SEC_OcspFailureMode ocspFailureMode;
+ CERT_StringFromCertFcn alternateOCSPAIAFcn;
+ PRBool forcePost;
+} OCSP_Global = { NULL,
+ NULL,
+ DEFAULT_OCSP_CACHE_SIZE,
+ DEFAULT_MINIMUM_SECONDS_TO_NEXT_OCSP_FETCH_ATTEMPT,
+ DEFAULT_MAXIMUM_SECONDS_TO_NEXT_OCSP_FETCH_ATTEMPT,
+ DEFAULT_OSCP_TIMEOUT_SECONDS,
+ { NULL, 0, NULL, NULL },
+ ocspMode_FailureIsVerificationFailure,
+ NULL,
+ PR_FALSE };
+
+/* Forward declarations */
+static SECItem *
+ocsp_GetEncodedOCSPResponseFromRequest(PLArenaPool *arena,
+ CERTOCSPRequest *request,
+ const char *location,
+ const char *method,
+ PRTime time,
+ PRBool addServiceLocator,
+ void *pwArg,
+ CERTOCSPRequest **pRequest);
+static SECStatus
+ocsp_GetOCSPStatusFromNetwork(CERTCertDBHandle *handle,
+ CERTOCSPCertID *certID,
+ CERTCertificate *cert,
+ PRTime time,
+ void *pwArg,
+ PRBool *certIDWasConsumed,
+ SECStatus *rv_ocsp);
+
+static SECStatus
+ocsp_GetDecodedVerifiedSingleResponseForID(CERTCertDBHandle *handle,
+ CERTOCSPCertID *certID,
+ CERTCertificate *cert,
+ PRTime time,
+ void *pwArg,
+ const SECItem *encodedResponse,
+ CERTOCSPResponse **pDecodedResponse,
+ CERTOCSPSingleResponse **pSingle);
+
+static SECStatus
+ocsp_CertRevokedAfter(ocspRevokedInfo *revokedInfo, PRTime time);
+
+static CERTOCSPCertID *
+cert_DupOCSPCertID(const CERTOCSPCertID *src);
+
+#ifndef DEBUG
+#define OCSP_TRACE(msg)
+#define OCSP_TRACE_TIME(msg, time)
+#define OCSP_TRACE_CERT(cert)
+#define OCSP_TRACE_CERTID(certid)
+#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)
+#define OCSP_TRACE_CERTID(certid) dumpCertID(certid)
+
+#if defined(XP_UNIX) || defined(XP_WIN32) || defined(XP_MACOSX)
+#define NSS_HAVE_GETENV 1
+#endif
+
+static PRBool
+wantOcspTrace(void)
+{
+ static PRBool firstTime = PR_TRUE;
+ static PRBool wantTrace = PR_FALSE;
+
+#ifdef NSS_HAVE_GETENV
+ if (firstTime) {
+ char *ev = PR_GetEnvSecure("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, PRTime time)
+{
+ PRExplodedTime timePrintable;
+ char timestr[256];
+
+ if (!wantOcspTrace())
+ return;
+ PR_ExplodeTime(time, PR_GMTParameters, &timePrintable);
+ if (PR_FormatTime(timestr, 256, "%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) {
+ hexbuf = PR_sprintf_append(hexbuf, "%02x:", hexval->data[i]);
+ } else {
+ hexbuf = 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);
+ {
+ PRTime timeBefore, timeAfter;
+ PRExplodedTime beforePrintable, afterPrintable;
+ char beforestr[256], afterstr[256];
+ PRStatus rv1, rv2;
+ DER_DecodeTimeChoice(&timeBefore, &cert->validity.notBefore);
+ DER_DecodeTimeChoice(&timeAfter, &cert->validity.notAfter);
+ PR_ExplodeTime(timeBefore, PR_GMTParameters, &beforePrintable);
+ PR_ExplodeTime(timeAfter, PR_GMTParameters, &afterPrintable);
+ rv1 = PR_FormatTime(beforestr, 256, "%a %b %d %H:%M:%S %Y",
+ &beforePrintable);
+ rv2 = PR_FormatTime(afterstr, 256, "%a %b %d %H:%M:%S %Y",
+ &afterPrintable);
+ ocsp_Trace("OCSP ## VALIDITY: %s to %s\n", rv1 ? beforestr : "",
+ rv2 ? afterstr : "");
+ }
+ ocsp_Trace("OCSP ## ISSUER: %s\n", cert->issuerName);
+ printHexString("OCSP ## SERIAL NUMBER:", &cert->serialNumber);
+}
+
+static void
+dumpCertID(CERTOCSPCertID *certID)
+{
+ if (!wantOcspTrace())
+ return;
+
+ printHexString("OCSP certID issuer", &certID->issuerNameHash);
+ printHexString("OCSP certID serial", &certID->serialNumber);
+}
+#endif
+
+SECStatus
+SEC_RegisterDefaultHttpClient(const SEC_HttpClientFcn *fcnTable)
+{
+ if (!OCSP_Global.monitor) {
+ PORT_SetError(SEC_ERROR_NOT_INITIALIZED);
+ return SECFailure;
+ }
+
+ PR_EnterMonitor(OCSP_Global.monitor);
+ OCSP_Global.defaultHttpClientFcn = fcnTable;
+ PR_ExitMonitor(OCSP_Global.monitor);
+
+ return SECSuccess;
+}
+
+SECStatus
+CERT_RegisterAlternateOCSPAIAInfoCallBack(
+ CERT_StringFromCertFcn newCallback,
+ CERT_StringFromCertFcn *oldCallback)
+{
+ CERT_StringFromCertFcn old;
+
+ if (!OCSP_Global.monitor) {
+ PORT_SetError(SEC_ERROR_NOT_INITIALIZED);
+ return SECFailure;
+ }
+
+ PR_EnterMonitor(OCSP_Global.monitor);
+ old = OCSP_Global.alternateOCSPAIAFcn;
+ OCSP_Global.alternateOCSPAIAFcn = newCallback;
+ PR_ExitMonitor(OCSP_Global.monitor);
+ if (oldCallback)
+ *oldCallback = old;
+ 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(PLArenaPool *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(PLArenaPool *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(void)
+{
+ /*
+ * 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"));
+ OCSP_TRACE_CERTID(certID);
+ 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.
+ */
+ OCSP_TRACE(("OCSP ocsp_RemoveCacheItem, THREADID %p\n", PR_GetCurrentThread()));
+ PR_EnterMonitor(OCSP_Global.monitor);
+
+ ocsp_RemoveCacheItemFromLinkedList(cache, item);
+#ifdef DEBUG
+ {
+ PRBool couldRemoveFromHashTable = PL_HashTableRemove(cache->entries,
+ item->certID);
+ PORT_Assert(couldRemoveFromHashTable);
+ }
+#else
+ PL_HashTableRemove(cache->entries, item->certID);
+#endif
+ --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) {
+ /* Cache is not disabled. Number of cache entries is limited.
+ * The monitor ensures that maxCacheEntries remains positive.
+ */
+ while (cache->numberOfEntries >
+ (PRUint32)OCSP_Global.maxCacheEntries) {
+ ocsp_RemoveCacheItem(cache, cache->LRUitem);
+ }
+ }
+ PR_ExitMonitor(OCSP_Global.monitor);
+}
+
+SECStatus
+CERT_ClearOCSPCache(void)
+{
+ 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)
+{
+ PLArenaPool *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
+ and all error codes to zero/good. */
+ 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;
+ }
+ item->missingResponseError = 0;
+ 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->nextUpdate);
+ }
+
+ 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 fresh;
+
+ now = PR_Now();
+
+ fresh = cacheItem->nextFetchAttemptTime > now;
+
+ /* Work around broken OCSP responders that return unknown responses for
+ * certificates, especially certificates that were just recently issued.
+ */
+ if (fresh && cacheItem->certStatusArena &&
+ cacheItem->certStatus.certStatusType == ocspCertStatus_unknown) {
+ fresh = PR_FALSE;
+ }
+
+ OCSP_TRACE(("OCSP ocsp_IsCacheItemFresh: %d\n", fresh));
+
+ return fresh;
+}
+
+/*
+ * Status in *certIDWasConsumed will always be correct, regardless of
+ * return value.
+ * If the caller is unable to transfer ownership of certID,
+ * then the caller must set certIDWasConsumed to NULL,
+ * and this function will potentially duplicate the certID object.
+ */
+static SECStatus
+ocsp_CreateOrUpdateCacheEntry(OCSPCacheData *cache,
+ CERTOCSPCertID *certID,
+ CERTOCSPSingleResponse *single,
+ PRBool *certIDWasConsumed)
+{
+ SECStatus rv;
+ OCSPCacheItem *cacheItem;
+ OCSP_TRACE(("OCSP ocsp_CreateOrUpdateCacheEntry\n"));
+
+ if (certIDWasConsumed)
+ *certIDWasConsumed = PR_FALSE;
+
+ PR_EnterMonitor(OCSP_Global.monitor);
+ PORT_Assert(OCSP_Global.maxCacheEntries >= 0);
+
+ cacheItem = ocsp_FindCacheEntry(cache, certID);
+
+ /* Don't replace an unknown or revoked entry with an error entry, even if
+ * the existing entry is expired. Instead, we'll continue to use the
+ * existing (possibly expired) cache entry until we receive a valid signed
+ * response to replace it.
+ */
+ if (!single && cacheItem && cacheItem->certStatusArena &&
+ (cacheItem->certStatus.certStatusType == ocspCertStatus_revoked ||
+ cacheItem->certStatus.certStatusType == ocspCertStatus_unknown)) {
+ PR_ExitMonitor(OCSP_Global.monitor);
+ return SECSuccess;
+ }
+
+ if (!cacheItem) {
+ CERTOCSPCertID *myCertID;
+ if (certIDWasConsumed) {
+ myCertID = certID;
+ *certIDWasConsumed = PR_TRUE;
+ } else {
+ myCertID = cert_DupOCSPCertID(certID);
+ if (!myCertID) {
+ PR_ExitMonitor(OCSP_Global.monitor);
+ PORT_SetError(PR_OUT_OF_MEMORY_ERROR);
+ return SECFailure;
+ }
+ }
+
+ rv = ocsp_CreateCacheItemAndConsumeCertID(cache, myCertID,
+ &cacheItem);
+ if (rv != SECSuccess) {
+ PR_ExitMonitor(OCSP_Global.monitor);
+ return rv;
+ }
+ }
+ if (single) {
+ PRTime thisUpdate;
+ rv = DER_GeneralizedTimeToTime(&thisUpdate, &single->thisUpdate);
+
+ if (!cacheItem->haveThisUpdate ||
+ (rv == SECSuccess && cacheItem->thisUpdate < thisUpdate)) {
+ rv = ocsp_SetCacheItemResponse(cacheItem, single);
+ if (rv != SECSuccess) {
+ ocsp_RemoveCacheItem(cache, cacheItem);
+ PR_ExitMonitor(OCSP_Global.monitor);
+ return rv;
+ }
+ } else {
+ OCSP_TRACE(("Not caching response because the response is not "
+ "newer than the cache"));
+ }
+ } else {
+ cacheItem->missingResponseError = PORT_GetError();
+ if (cacheItem->certStatusArena) {
+ PORT_FreeArena(cacheItem->certStatusArena, PR_FALSE);
+ cacheItem->certStatusArena = NULL;
+ }
+ }
+ 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;
+}
+
+SECStatus
+CERT_SetOCSPTimeout(PRUint32 seconds)
+{
+ /* no locking, see bug 406120 */
+ OCSP_Global.timeoutSeconds = seconds;
+ 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_ShutdownGlobal(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;
+
+ OCSP_Global.defaultHttpClientFcn = NULL;
+ OCSP_Global.maxCacheEntries = DEFAULT_OCSP_CACHE_SIZE;
+ OCSP_Global.minimumSecondsToNextFetchAttempt =
+ DEFAULT_MINIMUM_SECONDS_TO_NEXT_OCSP_FETCH_ATTEMPT;
+ OCSP_Global.maximumSecondsToNextFetchAttempt =
+ DEFAULT_MAXIMUM_SECONDS_TO_NEXT_OCSP_FETCH_ATTEMPT;
+ OCSP_Global.ocspFailureMode =
+ ocspMode_FailureIsVerificationFailure;
+ PR_ExitMonitor(OCSP_Global.monitor);
+
+ PR_DestroyMonitor(OCSP_Global.monitor);
+ OCSP_Global.monitor = NULL;
+ return SECSuccess;
+}
+
+/*
+ * A return value of NULL means:
+ * The application did not register it's own HTTP client.
+ */
+const SEC_HttpClientFcn *
+SEC_GetRegisteredHttpClient(void)
+{
+ const SEC_HttpClientFcn *retval;
+
+ if (!OCSP_Global.monitor) {
+ PORT_SetError(SEC_ERROR_NOT_INITIALIZED);
+ 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
+ * someone turns on OCSP checking, and hangs off of the status-configuration
+ * structure in the certdb structure. We use it to keep configuration
+ * information specific to OCSP checking.
+ */
+typedef struct ocspCheckingContextStr {
+ PRBool useDefaultResponder;
+ char *defaultResponderURI;
+ char *defaultResponderNickname;
+ CERTCertificate *defaultResponderCert;
+} ocspCheckingContext;
+
+SEC_ASN1_MKSUB(SEC_AnyTemplate)
+SEC_ASN1_MKSUB(SEC_IntegerTemplate)
+SEC_ASN1_MKSUB(SEC_NullTemplate)
+SEC_ASN1_MKSUB(SEC_OctetStringTemplate)
+SEC_ASN1_MKSUB(SEC_PointerToAnyTemplate)
+SEC_ASN1_MKSUB(SECOID_AlgorithmIDTemplate)
+SEC_ASN1_MKSUB(SEC_SequenceOfAnyTemplate)
+SEC_ASN1_MKSUB(SEC_PointerToGeneralizedTimeTemplate)
+SEC_ASN1_MKSUB(SEC_PointerToEnumeratedTemplate)
+
+/*
+ * Forward declarations of sub-types, so I can lay out the types in the
+ * same order as the ASN.1 is laid out in the OCSP spec itself.
+ *
+ * These are in alphabetical order (case-insensitive); please keep it that way!
+ */
+extern const SEC_ASN1Template ocsp_CertIDTemplate[];
+extern const SEC_ASN1Template ocsp_PointerToSignatureTemplate[];
+extern const SEC_ASN1Template ocsp_PointerToResponseBytesTemplate[];
+extern const SEC_ASN1Template ocsp_ResponseDataTemplate[];
+extern const SEC_ASN1Template ocsp_RevokedInfoTemplate[];
+extern const SEC_ASN1Template ocsp_SingleRequestTemplate[];
+extern const SEC_ASN1Template ocsp_SingleResponseTemplate[];
+extern const SEC_ASN1Template ocsp_TBSRequestTemplate[];
+
+/*
+ * Request-related templates...
+ */
+
+/*
+ * OCSPRequest ::= SEQUENCE {
+ * tbsRequest TBSRequest,
+ * optionalSignature [0] EXPLICIT Signature OPTIONAL }
+ */
+static const SEC_ASN1Template ocsp_OCSPRequestTemplate[] = {
+ { SEC_ASN1_SEQUENCE,
+ 0, NULL, sizeof(CERTOCSPRequest) },
+ { SEC_ASN1_POINTER,
+ offsetof(CERTOCSPRequest, tbsRequest),
+ ocsp_TBSRequestTemplate },
+ { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT |
+ SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 0,
+ offsetof(CERTOCSPRequest, optionalSignature),
+ ocsp_PointerToSignatureTemplate },
+ { 0 }
+};
+
+/*
+ * TBSRequest ::= SEQUENCE {
+ * version [0] EXPLICIT Version DEFAULT v1,
+ * requestorName [1] EXPLICIT GeneralName OPTIONAL,
+ * requestList SEQUENCE OF Request,
+ * requestExtensions [2] EXPLICIT Extensions OPTIONAL }
+ *
+ * Version ::= INTEGER { v1(0) }
+ *
+ * Note: this should be static but the AIX compiler doesn't like it (because it
+ * was forward-declared above); it is not meant to be exported, but this
+ * is the only way it will compile.
+ */
+const SEC_ASN1Template ocsp_TBSRequestTemplate[] = {
+ { SEC_ASN1_SEQUENCE,
+ 0, NULL, sizeof(ocspTBSRequest) },
+ { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT | /* XXX DER_DEFAULT */
+ SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 0,
+ offsetof(ocspTBSRequest, version),
+ SEC_ASN1_SUB(SEC_IntegerTemplate) },
+ { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT |
+ SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 1,
+ offsetof(ocspTBSRequest, derRequestorName),
+ SEC_ASN1_SUB(SEC_PointerToAnyTemplate) },
+ { SEC_ASN1_SEQUENCE_OF,
+ offsetof(ocspTBSRequest, requestList),
+ ocsp_SingleRequestTemplate },
+ { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT |
+ SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 2,
+ offsetof(ocspTBSRequest, requestExtensions),
+ CERT_SequenceOfCertExtensionTemplate },
+ { 0 }
+};
+
+/*
+ * Signature ::= SEQUENCE {
+ * signatureAlgorithm AlgorithmIdentifier,
+ * signature BIT STRING,
+ * certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL }
+ */
+static const SEC_ASN1Template ocsp_SignatureTemplate[] = {
+ { SEC_ASN1_SEQUENCE,
+ 0, NULL, sizeof(ocspSignature) },
+ { SEC_ASN1_INLINE | SEC_ASN1_XTRN,
+ offsetof(ocspSignature, signatureAlgorithm),
+ SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) },
+ { SEC_ASN1_BIT_STRING,
+ offsetof(ocspSignature, signature) },
+ { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT |
+ SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 0,
+ offsetof(ocspSignature, derCerts),
+ SEC_ASN1_SUB(SEC_SequenceOfAnyTemplate) },
+ { 0 }
+};
+
+/*
+ * This template is just an extra level to use in an explicitly-tagged
+ * reference to a Signature.
+ *
+ * Note: this should be static but the AIX compiler doesn't like it (because it
+ * was forward-declared above); it is not meant to be exported, but this
+ * is the only way it will compile.
+ */
+const SEC_ASN1Template ocsp_PointerToSignatureTemplate[] = {
+ { SEC_ASN1_POINTER, 0, ocsp_SignatureTemplate }
+};
+
+/*
+ * Request ::= SEQUENCE {
+ * reqCert CertID,
+ * singleRequestExtensions [0] EXPLICIT Extensions OPTIONAL }
+ *
+ * Note: this should be static but the AIX compiler doesn't like it (because it
+ * was forward-declared above); it is not meant to be exported, but this
+ * is the only way it will compile.
+ */
+const SEC_ASN1Template ocsp_SingleRequestTemplate[] = {
+ { SEC_ASN1_SEQUENCE,
+ 0, NULL, sizeof(ocspSingleRequest) },
+ { SEC_ASN1_POINTER,
+ offsetof(ocspSingleRequest, reqCert),
+ ocsp_CertIDTemplate },
+ { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT |
+ SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 0,
+ offsetof(ocspSingleRequest, singleRequestExtensions),
+ CERT_SequenceOfCertExtensionTemplate },
+ { 0 }
+};
+
+/*
+ * This data structure and template (CertID) is used by both OCSP
+ * requests and responses. It is the only one that is shared.
+ *
+ * CertID ::= SEQUENCE {
+ * hashAlgorithm AlgorithmIdentifier,
+ * issuerNameHash OCTET STRING, -- Hash of Issuer DN
+ * issuerKeyHash OCTET STRING, -- Hash of Issuer public key
+ * serialNumber CertificateSerialNumber }
+ *
+ * CertificateSerialNumber ::= INTEGER
+ *
+ * Note: this should be static but the AIX compiler doesn't like it (because it
+ * was forward-declared above); it is not meant to be exported, but this
+ * is the only way it will compile.
+ */
+const SEC_ASN1Template ocsp_CertIDTemplate[] = {
+ { SEC_ASN1_SEQUENCE,
+ 0, NULL, sizeof(CERTOCSPCertID) },
+ { SEC_ASN1_INLINE | SEC_ASN1_XTRN,
+ offsetof(CERTOCSPCertID, hashAlgorithm),
+ SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) },
+ { SEC_ASN1_OCTET_STRING,
+ offsetof(CERTOCSPCertID, issuerNameHash) },
+ { SEC_ASN1_OCTET_STRING,
+ offsetof(CERTOCSPCertID, issuerKeyHash) },
+ { SEC_ASN1_INTEGER,
+ offsetof(CERTOCSPCertID, serialNumber) },
+ { 0 }
+};
+
+/*
+ * Response-related templates...
+ */
+
+/*
+ * OCSPResponse ::= SEQUENCE {
+ * responseStatus OCSPResponseStatus,
+ * responseBytes [0] EXPLICIT ResponseBytes OPTIONAL }
+ */
+const SEC_ASN1Template ocsp_OCSPResponseTemplate[] = {
+ { SEC_ASN1_SEQUENCE,
+ 0, NULL, sizeof(CERTOCSPResponse) },
+ { SEC_ASN1_ENUMERATED,
+ offsetof(CERTOCSPResponse, responseStatus) },
+ { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT |
+ SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 0,
+ offsetof(CERTOCSPResponse, responseBytes),
+ ocsp_PointerToResponseBytesTemplate },
+ { 0 }
+};
+
+/*
+ * ResponseBytes ::= SEQUENCE {
+ * responseType OBJECT IDENTIFIER,
+ * response OCTET STRING }
+ */
+const SEC_ASN1Template ocsp_ResponseBytesTemplate[] = {
+ { SEC_ASN1_SEQUENCE,
+ 0, NULL, sizeof(ocspResponseBytes) },
+ { SEC_ASN1_OBJECT_ID,
+ offsetof(ocspResponseBytes, responseType) },
+ { SEC_ASN1_OCTET_STRING,
+ offsetof(ocspResponseBytes, response) },
+ { 0 }
+};
+
+/*
+ * This template is just an extra level to use in an explicitly-tagged
+ * reference to a ResponseBytes.
+ *
+ * Note: this should be static but the AIX compiler doesn't like it (because it
+ * was forward-declared above); it is not meant to be exported, but this
+ * is the only way it will compile.
+ */
+const SEC_ASN1Template ocsp_PointerToResponseBytesTemplate[] = {
+ { SEC_ASN1_POINTER, 0, ocsp_ResponseBytesTemplate }
+};
+
+/*
+ * BasicOCSPResponse ::= SEQUENCE {
+ * tbsResponseData ResponseData,
+ * signatureAlgorithm AlgorithmIdentifier,
+ * signature BIT STRING,
+ * certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL }
+ */
+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 },
+ { SEC_ASN1_INLINE | SEC_ASN1_XTRN,
+ offsetof(ocspBasicOCSPResponse, responseSignature.signatureAlgorithm),
+ SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) },
+ { SEC_ASN1_BIT_STRING,
+ offsetof(ocspBasicOCSPResponse, responseSignature.signature) },
+ { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT |
+ SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 0,
+ offsetof(ocspBasicOCSPResponse, responseSignature.derCerts),
+ SEC_ASN1_SUB(SEC_SequenceOfAnyTemplate) },
+ { 0 }
+};
+
+/*
+ * ResponseData ::= SEQUENCE {
+ * version [0] EXPLICIT Version DEFAULT v1,
+ * responderID ResponderID,
+ * producedAt GeneralizedTime,
+ * responses SEQUENCE OF SingleResponse,
+ * responseExtensions [1] EXPLICIT Extensions OPTIONAL }
+ *
+ * Note: this should be static but the AIX compiler doesn't like it (because it
+ * was forward-declared above); it is not meant to be exported, but this
+ * is the only way it will compile.
+ */
+const SEC_ASN1Template ocsp_ResponseDataTemplate[] = {
+ { SEC_ASN1_SEQUENCE,
+ 0, NULL, sizeof(ocspResponseData) },
+ { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT | /* XXX DER_DEFAULT */
+ SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 0,
+ offsetof(ocspResponseData, version),
+ SEC_ASN1_SUB(SEC_IntegerTemplate) },
+ { SEC_ASN1_ANY,
+ offsetof(ocspResponseData, derResponderID) },
+ { SEC_ASN1_GENERALIZED_TIME,
+ offsetof(ocspResponseData, producedAt) },
+ { SEC_ASN1_SEQUENCE_OF,
+ offsetof(ocspResponseData, responses),
+ ocsp_SingleResponseTemplate },
+ { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT |
+ SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 1,
+ offsetof(ocspResponseData, responseExtensions),
+ CERT_SequenceOfCertExtensionTemplate },
+ { 0 }
+};
+
+/*
+ * ResponderID ::= CHOICE {
+ * byName [1] EXPLICIT Name,
+ * byKey [2] EXPLICIT KeyHash }
+ *
+ * KeyHash ::= OCTET STRING -- SHA-1 hash of responder's public key
+ * (excluding the tag and length fields)
+ *
+ * XXX Because the ASN.1 encoder and decoder currently do not provide
+ * a way to automatically handle a CHOICE, we need to do it in two
+ * steps, looking at the type tag and feeding the exact choice back
+ * to the ASN.1 code. Hopefully that will change someday and this
+ * can all be simplified down into a single template. Anyway, for
+ * now we list each choice as its own template:
+ */
+const SEC_ASN1Template ocsp_ResponderIDByNameTemplate[] = {
+ { SEC_ASN1_EXPLICIT | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 1,
+ offsetof(ocspResponderID, responderIDValue.name),
+ CERT_NameTemplate }
+};
+const SEC_ASN1Template ocsp_ResponderIDByKeyTemplate[] = {
+ { SEC_ASN1_EXPLICIT | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC |
+ SEC_ASN1_XTRN | 2,
+ offsetof(ocspResponderID, responderIDValue.keyHash),
+ SEC_ASN1_SUB(SEC_OctetStringTemplate) }
+};
+static const SEC_ASN1Template ocsp_ResponderIDOtherTemplate[] = {
+ { SEC_ASN1_ANY,
+ 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 |
+ SEC_ASN1_XTRN | 1,
+ 0, SEC_ASN1_SUB(SEC_AnyTemplate) }
+};
+
+/*
+ * SingleResponse ::= SEQUENCE {
+ * certID CertID,
+ * certStatus CertStatus,
+ * thisUpdate GeneralizedTime,
+ * nextUpdate [0] EXPLICIT GeneralizedTime OPTIONAL,
+ * singleExtensions [1] EXPLICIT Extensions OPTIONAL }
+ *
+ * Note: this should be static but the AIX compiler doesn't like it (because it
+ * was forward-declared above); it is not meant to be exported, but this
+ * is the only way it will compile.
+ */
+const SEC_ASN1Template ocsp_SingleResponseTemplate[] = {
+ { SEC_ASN1_SEQUENCE,
+ 0, NULL, sizeof(CERTOCSPSingleResponse) },
+ { SEC_ASN1_POINTER,
+ offsetof(CERTOCSPSingleResponse, certID),
+ ocsp_CertIDTemplate },
+ { SEC_ASN1_ANY,
+ offsetof(CERTOCSPSingleResponse, derCertStatus) },
+ { SEC_ASN1_GENERALIZED_TIME,
+ offsetof(CERTOCSPSingleResponse, thisUpdate) },
+ { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT |
+ SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 0,
+ offsetof(CERTOCSPSingleResponse, nextUpdate),
+ SEC_ASN1_SUB(SEC_PointerToGeneralizedTimeTemplate) },
+ { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT |
+ SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 1,
+ offsetof(CERTOCSPSingleResponse, singleExtensions),
+ CERT_SequenceOfCertExtensionTemplate },
+ { 0 }
+};
+
+/*
+ * CertStatus ::= CHOICE {
+ * good [0] IMPLICIT NULL,
+ * revoked [1] IMPLICIT RevokedInfo,
+ * unknown [2] IMPLICIT UnknownInfo }
+ *
+ * Because the ASN.1 encoder and decoder currently do not provide
+ * a way to automatically handle a CHOICE, we need to do it in two
+ * steps, looking at the type tag and feeding the exact choice back
+ * to the ASN.1 code. Hopefully that will change someday and this
+ * can all be simplified down into a single template. Anyway, for
+ * now we list each choice as its own template:
+ */
+static const SEC_ASN1Template ocsp_CertStatusGoodTemplate[] = {
+ { SEC_ASN1_POINTER | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 0,
+ offsetof(ocspCertStatus, certStatusInfo.goodInfo),
+ SEC_ASN1_SUB(SEC_NullTemplate) }
+};
+static const SEC_ASN1Template ocsp_CertStatusRevokedTemplate[] = {
+ { SEC_ASN1_POINTER | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 1,
+ offsetof(ocspCertStatus, certStatusInfo.revokedInfo),
+ ocsp_RevokedInfoTemplate }
+};
+static const SEC_ASN1Template ocsp_CertStatusUnknownTemplate[] = {
+ { SEC_ASN1_POINTER | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 2,
+ offsetof(ocspCertStatus, certStatusInfo.unknownInfo),
+ SEC_ASN1_SUB(SEC_NullTemplate) }
+};
+static const SEC_ASN1Template ocsp_CertStatusOtherTemplate[] = {
+ { SEC_ASN1_POINTER | SEC_ASN1_XTRN,
+ offsetof(ocspCertStatus, certStatusInfo.otherInfo),
+ SEC_ASN1_SUB(SEC_AnyTemplate) }
+};
+
+/*
+ * RevokedInfo ::= SEQUENCE {
+ * revocationTime GeneralizedTime,
+ * revocationReason [0] EXPLICIT CRLReason OPTIONAL }
+ *
+ * Note: this should be static but the AIX compiler doesn't like it (because it
+ * was forward-declared above); it is not meant to be exported, but this
+ * is the only way it will compile.
+ */
+const SEC_ASN1Template ocsp_RevokedInfoTemplate[] = {
+ { SEC_ASN1_SEQUENCE,
+ 0, NULL, sizeof(ocspRevokedInfo) },
+ { SEC_ASN1_GENERALIZED_TIME,
+ offsetof(ocspRevokedInfo, revocationTime) },
+ { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT |
+ SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC |
+ SEC_ASN1_XTRN | 0,
+ offsetof(ocspRevokedInfo, revocationReason),
+ SEC_ASN1_SUB(SEC_PointerToEnumeratedTemplate) },
+ { 0 }
+};
+
+/*
+ * OCSP-specific extension templates:
+ */
+
+/*
+ * ServiceLocator ::= SEQUENCE {
+ * issuer Name,
+ * locator AuthorityInfoAccessSyntax OPTIONAL }
+ */
+static const SEC_ASN1Template ocsp_ServiceLocatorTemplate[] = {
+ { SEC_ASN1_SEQUENCE,
+ 0, NULL, sizeof(ocspServiceLocator) },
+ { SEC_ASN1_POINTER,
+ offsetof(ocspServiceLocator, issuer),
+ CERT_NameTemplate },
+ { SEC_ASN1_OPTIONAL | SEC_ASN1_ANY,
+ offsetof(ocspServiceLocator, locator) },
+ { 0 }
+};
+
+/*
+ * REQUEST SUPPORT FUNCTIONS (encode/create/decode/destroy):
+ */
+
+/*
+ * FUNCTION: CERT_EncodeOCSPRequest
+ * DER encodes an OCSP Request, possibly adding a signature as well.
+ * XXX Signing is not yet supported, however; see comments in code.
+ * INPUTS:
+ * PLArenaPool *arena
+ * The return value is allocated from here.
+ * If a NULL is passed in, allocation is done from the heap instead.
+ * CERTOCSPRequest *request
+ * The request to be encoded.
+ * void *pwArg
+ * Pointer to argument for password prompting, if needed. (Definitely
+ * not needed if not signing.)
+ * RETURN:
+ * Returns a NULL on error and a pointer to the SECItem with the
+ * encoded value otherwise. Any error is likely to be low-level
+ * (e.g. no memory).
+ */
+SECItem *
+CERT_EncodeOCSPRequest(PLArenaPool *arena, CERTOCSPRequest *request,
+ void *pwArg)
+{
+ SECStatus rv;
+
+ /* XXX All of these should generate errors if they fail. */
+ PORT_Assert(request);
+ PORT_Assert(request->tbsRequest);
+
+ if (request->tbsRequest->extensionHandle != NULL) {
+ rv = CERT_FinishExtensions(request->tbsRequest->extensionHandle);
+ request->tbsRequest->extensionHandle = NULL;
+ if (rv != SECSuccess)
+ return NULL;
+ }
+
+ /*
+ * XXX When signed requests are supported and request->optionalSignature
+ * is not NULL:
+ * - need to encode tbsRequest->requestorName
+ * - need to encode tbsRequest
+ * - need to sign that encoded result (using cert in sig), filling in the
+ * request->optionalSignature structure with the result, the signing
+ * algorithm and (perhaps?) the cert (and its chain?) in derCerts
+ */
+
+ return SEC_ASN1EncodeItem(arena, NULL, request, ocsp_OCSPRequestTemplate);
+}
+
+/*
+ * FUNCTION: CERT_DecodeOCSPRequest
+ * Decode a DER encoded OCSP Request.
+ * INPUTS:
+ * SECItem *src
+ * Pointer to a SECItem holding DER encoded OCSP Request.
+ * RETURN:
+ * Returns a pointer to a CERTOCSPRequest containing the decoded request.
+ * On error, returns NULL. Most likely error is trouble decoding
+ * (SEC_ERROR_OCSP_MALFORMED_REQUEST), or low-level problem (no memory).
+ */
+CERTOCSPRequest *
+CERT_DecodeOCSPRequest(const SECItem *src)
+{
+ PLArenaPool *arena = NULL;
+ SECStatus rv = SECFailure;
+ CERTOCSPRequest *dest = NULL;
+ int i;
+ SECItem newSrc;
+
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (arena == NULL) {
+ goto loser;
+ }
+ dest = (CERTOCSPRequest *)PORT_ArenaZAlloc(arena,
+ sizeof(CERTOCSPRequest));
+ if (dest == NULL) {
+ goto loser;
+ }
+ dest->arena = arena;
+
+ /* copy the DER into the arena, since Quick DER returns data that points
+ into the DER input, which may get freed by the caller */
+ rv = SECITEM_CopyItem(arena, &newSrc, src);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ rv = SEC_QuickDERDecodeItem(arena, dest, ocsp_OCSPRequestTemplate, &newSrc);
+ if (rv != SECSuccess) {
+ if (PORT_GetError() == SEC_ERROR_BAD_DER)
+ PORT_SetError(SEC_ERROR_OCSP_MALFORMED_REQUEST);
+ goto loser;
+ }
+
+ /*
+ * XXX I would like to find a way to get rid of the necessity
+ * of doing this copying of the arena pointer.
+ */
+ for (i = 0; dest->tbsRequest->requestList[i] != NULL; i++) {
+ dest->tbsRequest->requestList[i]->arena = arena;
+ }
+
+ return dest;
+
+loser:
+ if (arena != NULL) {
+ PORT_FreeArena(arena, PR_FALSE);
+ }
+ return NULL;
+}
+
+SECStatus
+CERT_DestroyOCSPCertID(CERTOCSPCertID *certID)
+{
+ if (certID && certID->poolp) {
+ PORT_FreeArena(certID->poolp, PR_FALSE);
+ return SECSuccess;
+ }
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ 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).
+ */
+
+SECItem *
+ocsp_DigestValue(PLArenaPool *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_GetSubjectPublicKeyDigest(PLArenaPool *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.
+ */
+SECItem *
+CERT_GetSubjectNameDigest(PLArenaPool *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
+ * (issuerNameHash and issuerKeyHash), and is hardwired to use SHA1.
+ * Someday it might need to be more flexible about hash algorithm, but
+ * for now we have no intention/need to create anything else.
+ *
+ * Error causes a null to be returned; most likely cause is trouble
+ * finding the certificate issuer (SEC_ERROR_UNKNOWN_ISSUER).
+ * Other errors are low-level problems (no memory, bad database, etc.).
+ */
+static CERTOCSPCertID *
+ocsp_CreateCertID(PLArenaPool *arena, CERTCertificate *cert, PRTime time)
+{
+ CERTOCSPCertID *certID;
+ CERTCertificate *issuerCert = NULL;
+ void *mark = PORT_ArenaMark(arena);
+ SECStatus rv;
+
+ PORT_Assert(arena != NULL);
+
+ certID = PORT_ArenaZNew(arena, CERTOCSPCertID);
+ if (certID == NULL) {
+ goto loser;
+ }
+
+ rv = SECOID_SetAlgorithmID(arena, &certID->hashAlgorithm, SEC_OID_SHA1,
+ NULL);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ issuerCert = CERT_FindCertIssuer(cert, time, certUsageAnyCA);
+ if (issuerCert == NULL) {
+ 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;
+
+ if (CERT_GetSubjectNameDigest(arena, issuerCert, SEC_OID_MD5,
+ &(certID->issuerMD5NameHash)) == NULL) {
+ goto loser;
+ }
+
+ if (CERT_GetSubjectNameDigest(arena, issuerCert, SEC_OID_MD2,
+ &(certID->issuerMD2NameHash)) == NULL) {
+ goto loser;
+ }
+
+ if (CERT_GetSubjectPublicKeyDigest(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_GetSubjectPublicKeyDigest(arena, issuerCert, SEC_OID_MD5,
+ &certID->issuerMD5KeyHash) == NULL) {
+ goto loser;
+ }
+ if (CERT_GetSubjectPublicKeyDigest(arena, issuerCert, SEC_OID_MD2,
+ &certID->issuerMD2KeyHash) == NULL) {
+ goto loser;
+ }
+
+ /* now we are done with issuerCert */
+ CERT_DestroyCertificate(issuerCert);
+ issuerCert = NULL;
+
+ rv = SECITEM_CopyItem(arena, &certID->serialNumber, &cert->serialNumber);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ PORT_ArenaUnmark(arena, mark);
+ return certID;
+
+loser:
+ if (issuerCert != NULL) {
+ CERT_DestroyCertificate(issuerCert);
+ }
+ PORT_ArenaRelease(arena, mark);
+ return NULL;
+}
+
+CERTOCSPCertID *
+CERT_CreateOCSPCertID(CERTCertificate *cert, PRTime time)
+{
+ PLArenaPool *arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ CERTOCSPCertID *certID;
+ PORT_Assert(arena != NULL);
+ if (!arena)
+ return NULL;
+
+ certID = ocsp_CreateCertID(arena, cert, time);
+ if (!certID) {
+ PORT_FreeArena(arena, PR_FALSE);
+ return NULL;
+ }
+ certID->poolp = arena;
+ return certID;
+}
+
+static CERTOCSPCertID *
+cert_DupOCSPCertID(const CERTOCSPCertID *src)
+{
+ CERTOCSPCertID *dest;
+ PLArenaPool *arena = NULL;
+
+ if (!src) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return NULL;
+ }
+
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (!arena)
+ goto loser;
+
+ dest = PORT_ArenaZNew(arena, CERTOCSPCertID);
+ if (!dest)
+ goto loser;
+
+#define DUPHELP(element) \
+ if (src->element.data && \
+ SECITEM_CopyItem(arena, &dest->element, &src->element) != \
+ SECSuccess) { \
+ goto loser; \
+ }
+
+ DUPHELP(hashAlgorithm.algorithm)
+ DUPHELP(hashAlgorithm.parameters)
+ DUPHELP(issuerNameHash)
+ DUPHELP(issuerKeyHash)
+ DUPHELP(serialNumber)
+ DUPHELP(issuerSHA1NameHash)
+ DUPHELP(issuerMD5NameHash)
+ DUPHELP(issuerMD2NameHash)
+ DUPHELP(issuerSHA1KeyHash)
+ DUPHELP(issuerMD5KeyHash)
+ DUPHELP(issuerMD2KeyHash)
+
+ dest->poolp = arena;
+ return dest;
+
+loser:
+ if (arena)
+ PORT_FreeArena(arena, PR_FALSE);
+ PORT_SetError(PR_OUT_OF_MEMORY_ERROR);
+ return NULL;
+}
+
+/*
+ * Callback to set Extensions in request object
+ */
+void
+SetSingleReqExts(void *object, CERTCertExtension **exts)
+{
+ ocspSingleRequest *singleRequest =
+ (ocspSingleRequest *)object;
+
+ singleRequest->singleRequestExtensions = exts;
+}
+
+/*
+ * Add the Service Locator extension to the singleRequestExtensions
+ * for the given singleRequest.
+ *
+ * All errors are internal or low-level problems (e.g. no memory).
+ */
+static SECStatus
+ocsp_AddServiceLocatorExtension(ocspSingleRequest *singleRequest,
+ CERTCertificate *cert)
+{
+ ocspServiceLocator *serviceLocator = NULL;
+ void *extensionHandle = NULL;
+ SECStatus rv = SECFailure;
+
+ serviceLocator = PORT_ZNew(ocspServiceLocator);
+ if (serviceLocator == NULL)
+ goto loser;
+
+ /*
+ * Normally it would be a bad idea to do a direct reference like
+ * this rather than allocate and copy the name *or* at least dup
+ * a reference of the cert. But all we need is to be able to read
+ * the issuer name during the encoding we are about to do, so a
+ * copy is just a waste of time.
+ */
+ serviceLocator->issuer = &cert->issuer;
+
+ rv = CERT_FindCertExtension(cert, SEC_OID_X509_AUTH_INFO_ACCESS,
+ &serviceLocator->locator);
+ if (rv != SECSuccess) {
+ if (PORT_GetError() != SEC_ERROR_EXTENSION_NOT_FOUND)
+ goto loser;
+ }
+
+ /* prepare for following loser gotos */
+ rv = SECFailure;
+ PORT_SetError(0);
+
+ extensionHandle = cert_StartExtensions(singleRequest,
+ singleRequest->arena, SetSingleReqExts);
+ if (extensionHandle == NULL)
+ goto loser;
+
+ rv = CERT_EncodeAndAddExtension(extensionHandle,
+ SEC_OID_PKIX_OCSP_SERVICE_LOCATOR,
+ serviceLocator, PR_FALSE,
+ ocsp_ServiceLocatorTemplate);
+
+loser:
+ if (extensionHandle != NULL) {
+ /*
+ * Either way we have to finish out the extension context (so it gets
+ * freed). But careful not to override any already-set bad status.
+ */
+ SECStatus tmprv = CERT_FinishExtensions(extensionHandle);
+ if (rv == SECSuccess)
+ rv = tmprv;
+ }
+
+ /*
+ * Finally, free the serviceLocator structure itself and we are done.
+ */
+ if (serviceLocator != NULL) {
+ if (serviceLocator->locator.data != NULL)
+ SECITEM_FreeItem(&serviceLocator->locator, PR_FALSE);
+ PORT_Free(serviceLocator);
+ }
+
+ return rv;
+}
+
+/*
+ * Creates an array of ocspSingleRequest based on a list of certs.
+ * Note that the code which later compares the request list with the
+ * response expects this array to be in the exact same order as the
+ * certs are found in the list. It would be harder to change that
+ * order than preserve it, but since the requirement is not obvious,
+ * it deserves to be mentioned.
+ *
+ * Any problem causes a null return and error set:
+ * SEC_ERROR_UNKNOWN_ISSUER
+ * Other errors are low-level problems (no memory, bad database, etc.).
+ */
+static ocspSingleRequest **
+ocsp_CreateSingleRequestList(PLArenaPool *arena, CERTCertList *certList,
+ PRTime time, PRBool includeLocator)
+{
+ ocspSingleRequest **requestList = NULL;
+ 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);
+ }
+
+ if (count == 0)
+ goto loser;
+
+ requestList = PORT_ArenaNewArray(arena, ocspSingleRequest *, count + 1);
+ if (requestList == NULL)
+ goto loser;
+
+ 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;
+
+ OCSP_TRACE(("OCSP CERT_CreateOCSPRequest %s\n", node->cert->subjectName));
+ 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;
+
+ rv = ocsp_AddServiceLocatorExtension(requestList[i], node->cert);
+ if (rv != SECSuccess)
+ goto loser;
+ }
+
+ node = CERT_LIST_NEXT(node);
+ }
+
+ PORT_Assert(i == count);
+
+ PORT_ArenaUnmark(arena, mark);
+ requestList[i] = NULL;
+ return requestList;
+
+loser:
+ PORT_ArenaRelease(arena, mark);
+ return NULL;
+}
+
+static ocspSingleRequest **
+ocsp_CreateRequestFromCert(PLArenaPool *arena,
+ CERTOCSPCertID *certID,
+ CERTCertificate *singleCert,
+ PRTime 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(void)
+{
+ PLArenaPool *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;
+}
+
+CERTOCSPRequest *
+cert_CreateSingleCertOCSPRequest(CERTOCSPCertID *certID,
+ CERTCertificate *singleCert,
+ PRTime time,
+ PRBool addServiceLocator,
+ CERTCertificate *signerCert)
+{
+ CERTOCSPRequest *request;
+ OCSP_TRACE(("OCSP cert_CreateSingleCertOCSPRequest %s\n", singleCert->subjectName));
+
+ /* XXX Support for signerCert may be implemented later,
+ * see also the comment in CERT_CreateOCSPRequest.
+ */
+ if (signerCert != NULL) {
+ PORT_SetError(PR_NOT_IMPLEMENTED_ERROR);
+ return NULL;
+ }
+
+ 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
+ * Creates a CERTOCSPRequest, requesting the status of the certs in
+ * the given list.
+ * INPUTS:
+ * CERTCertList *certList
+ * A list of certs for which status will be requested.
+ * Note that all of these certificates should have the same issuer,
+ * or it's expected the response will be signed by a trusted responder.
+ * If the certs need to be broken up into multiple requests, that
+ * must be handled by the caller (and thus by having multiple calls
+ * to this routine), who knows about where the request(s) are being
+ * sent and whether there are any trusted responders in place.
+ * PRTime time
+ * Indicates the time for which the certificate status is to be
+ * determined -- this may be used in the search for the cert's issuer
+ * but has no effect on the request itself.
+ * PRBool addServiceLocator
+ * If true, the Service Locator extension should be added to the
+ * single request(s) for each cert.
+ * CERTCertificate *signerCert
+ * If non-NULL, means sign the request using this cert. Otherwise,
+ * do not sign.
+ * XXX note that request signing is not yet supported; see comment in code
+ * RETURN:
+ * A pointer to a CERTOCSPRequest structure containing an OCSP request
+ * for the cert list. On error, null is returned, with an error set
+ * indicating the reason. This is likely SEC_ERROR_UNKNOWN_ISSUER.
+ * (The issuer is needed to create a request for the certificate.)
+ * Other errors are low-level problems (no memory, bad database, etc.).
+ */
+CERTOCSPRequest *
+CERT_CreateOCSPRequest(CERTCertList *certList, PRTime time,
+ PRBool addServiceLocator,
+ CERTCertificate *signerCert)
+{
+ CERTOCSPRequest *request = NULL;
+
+ if (!certList) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return NULL;
+ }
+ /*
+ * XXX When we are prepared to put signing of requests back in,
+ * we will need to allocate a signature
+ * structure for the request, fill in the "derCerts" field in it,
+ * save the signerCert there, as well as fill in the "requestorName"
+ * field of the tbsRequest.
+ */
+ if (signerCert != NULL) {
+ PORT_SetError(PR_NOT_IMPLEMENTED_ERROR);
+ return NULL;
+ }
+ request = ocsp_prepareEmptyOCSPRequest();
+ if (!request)
+ return NULL;
+ /*
+ * Now create the list of single requests, one for each cert.
+ */
+ 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;
+}
+
+/*
+ * FUNCTION: CERT_AddOCSPAcceptableResponses
+ * Add the AcceptableResponses extension to an OCSP Request.
+ * INPUTS:
+ * CERTOCSPRequest *request
+ * The request to which the extension should be added.
+ * ...
+ * A list (of one or more) of SECOidTag -- each of the response types
+ * to be added. The last OID *must* be SEC_OID_PKIX_OCSP_BASIC_RESPONSE.
+ * (This marks the end of the list, and it must be specified because a
+ * client conforming to the OCSP standard is required to handle the basic
+ * response type.) The OIDs are not checked in any way.
+ * RETURN:
+ * SECSuccess if the extension is added; SECFailure if anything goes wrong.
+ * All errors are internal or low-level problems (e.g. no memory).
+ */
+
+void
+SetRequestExts(void *object, CERTCertExtension **exts)
+{
+ CERTOCSPRequest *request = (CERTOCSPRequest *)object;
+
+ request->tbsRequest->requestExtensions = exts;
+}
+
+#if defined(__GNUC__) && !defined(NSS_NO_GCC48)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wvarargs"
+#endif
+SECStatus
+CERT_AddOCSPAcceptableResponses(CERTOCSPRequest *request,
+ SECOidTag responseType0, ...)
+{
+ void *extHandle;
+ va_list ap;
+ int i, count;
+ SECOidTag responseType;
+ SECOidData *responseOid;
+ SECItem **acceptableResponses = NULL;
+ SECStatus rv = SECFailure;
+
+ extHandle = request->tbsRequest->extensionHandle;
+ if (extHandle == NULL) {
+ extHandle = cert_StartExtensions(request, request->arena, SetRequestExts);
+ if (extHandle == NULL)
+ goto loser;
+ }
+
+ /* Count number of OIDS going into the extension value. */
+ count = 1;
+ if (responseType0 != SEC_OID_PKIX_OCSP_BASIC_RESPONSE) {
+ va_start(ap, responseType0);
+ do {
+ count++;
+ responseType = va_arg(ap, SECOidTag);
+ } while (responseType != SEC_OID_PKIX_OCSP_BASIC_RESPONSE);
+ va_end(ap);
+ }
+
+ acceptableResponses = PORT_NewArray(SECItem *, count + 1);
+ if (acceptableResponses == NULL)
+ goto loser;
+
+ i = 0;
+ responseOid = SECOID_FindOIDByTag(responseType0);
+ acceptableResponses[i++] = &(responseOid->oid);
+ if (count > 1) {
+ va_start(ap, responseType0);
+ for (; i < count; i++) {
+ responseType = va_arg(ap, SECOidTag);
+ responseOid = SECOID_FindOIDByTag(responseType);
+ acceptableResponses[i] = &(responseOid->oid);
+ }
+ va_end(ap);
+ }
+ acceptableResponses[i] = NULL;
+
+ rv = CERT_EncodeAndAddExtension(extHandle, SEC_OID_PKIX_OCSP_RESPONSE,
+ &acceptableResponses, PR_FALSE,
+ SEC_ASN1_GET(SEC_SequenceOfObjectIDTemplate));
+ if (rv != SECSuccess)
+ goto loser;
+
+ PORT_Free(acceptableResponses);
+ if (request->tbsRequest->extensionHandle == NULL)
+ request->tbsRequest->extensionHandle = extHandle;
+ return SECSuccess;
+
+loser:
+ if (acceptableResponses != NULL)
+ PORT_Free(acceptableResponses);
+ if (extHandle != NULL)
+ (void)CERT_FinishExtensions(extHandle);
+ return rv;
+}
+#if defined(__GNUC__) && !defined(NSS_NO_GCC48)
+#pragma GCC diagnostic pop
+#endif
+
+/*
+ * FUNCTION: CERT_DestroyOCSPRequest
+ * Frees an OCSP Request structure.
+ * INPUTS:
+ * CERTOCSPRequest *request
+ * Pointer to CERTOCSPRequest to be freed.
+ * RETURN:
+ * No return value; no errors.
+ */
+void
+CERT_DestroyOCSPRequest(CERTOCSPRequest *request)
+{
+ if (request == NULL)
+ return;
+
+ if (request->tbsRequest != NULL) {
+ if (request->tbsRequest->requestorName != NULL)
+ CERT_DestroyGeneralNameList(request->tbsRequest->requestorName);
+ if (request->tbsRequest->extensionHandle != NULL)
+ (void)CERT_FinishExtensions(request->tbsRequest->extensionHandle);
+ }
+
+ if (request->optionalSignature != NULL) {
+ if (request->optionalSignature->cert != NULL)
+ CERT_DestroyCertificate(request->optionalSignature->cert);
+
+ /*
+ * XXX Need to free derCerts? Or do they come out of arena?
+ * (Currently we never fill in derCerts, which is why the
+ * answer is not obvious. Once we do, add any necessary code
+ * here and remove this comment.)
+ */
+ }
+
+ /*
+ * We should actually never have a request without an arena,
+ * but check just in case. (If there isn't one, there is not
+ * much we can do about it...)
+ */
+ PORT_Assert(request->arena != NULL);
+ if (request->arena != NULL)
+ PORT_FreeArena(request->arena, PR_FALSE);
+}
+
+/*
+ * RESPONSE SUPPORT FUNCTIONS (encode/create/decode/destroy):
+ */
+
+/*
+ * Helper function for encoding or decoding a ResponderID -- based on the
+ * given type, return the associated template for that choice.
+ */
+static const SEC_ASN1Template *
+ocsp_ResponderIDTemplateByType(CERTOCSPResponderIDType responderIDType)
+{
+ const SEC_ASN1Template *responderIDTemplate;
+
+ switch (responderIDType) {
+ case ocspResponderID_byName:
+ responderIDTemplate = ocsp_ResponderIDByNameTemplate;
+ break;
+ case ocspResponderID_byKey:
+ responderIDTemplate = ocsp_ResponderIDByKeyTemplate;
+ break;
+ case ocspResponderID_other:
+ default:
+ PORT_Assert(responderIDType == ocspResponderID_other);
+ responderIDTemplate = ocsp_ResponderIDOtherTemplate;
+ break;
+ }
+
+ return responderIDTemplate;
+}
+
+/*
+ * Helper function for encoding or decoding a CertStatus -- based on the
+ * given type, return the associated template for that choice.
+ */
+static const SEC_ASN1Template *
+ocsp_CertStatusTemplateByType(ocspCertStatusType certStatusType)
+{
+ const SEC_ASN1Template *certStatusTemplate;
+
+ switch (certStatusType) {
+ case ocspCertStatus_good:
+ certStatusTemplate = ocsp_CertStatusGoodTemplate;
+ break;
+ case ocspCertStatus_revoked:
+ certStatusTemplate = ocsp_CertStatusRevokedTemplate;
+ break;
+ case ocspCertStatus_unknown:
+ certStatusTemplate = ocsp_CertStatusUnknownTemplate;
+ break;
+ case ocspCertStatus_other:
+ default:
+ PORT_Assert(certStatusType == ocspCertStatus_other);
+ certStatusTemplate = ocsp_CertStatusOtherTemplate;
+ break;
+ }
+
+ return certStatusTemplate;
+}
+
+/*
+ * Helper function for decoding a certStatus -- turn the actual DER tag
+ * into our local translation.
+ */
+static ocspCertStatusType
+ocsp_CertStatusTypeByTag(int derTag)
+{
+ ocspCertStatusType certStatusType;
+
+ switch (derTag) {
+ case 0:
+ certStatusType = ocspCertStatus_good;
+ break;
+ case 1:
+ certStatusType = ocspCertStatus_revoked;
+ break;
+ case 2:
+ certStatusType = ocspCertStatus_unknown;
+ break;
+ default:
+ certStatusType = ocspCertStatus_other;
+ break;
+ }
+
+ return certStatusType;
+}
+
+/*
+ * Helper function for decoding SingleResponses -- they each contain
+ * a status which is encoded as CHOICE, which needs to be decoded "by hand".
+ *
+ * Note -- on error, this routine does not release the memory it may
+ * have allocated; it expects its caller to do that.
+ */
+static SECStatus
+ocsp_FinishDecodingSingleResponses(PLArenaPool *reqArena,
+ CERTOCSPSingleResponse **responses)
+{
+ ocspCertStatus *certStatus;
+ ocspCertStatusType certStatusType;
+ const SEC_ASN1Template *certStatusTemplate;
+ int derTag;
+ int i;
+ SECStatus rv = SECFailure;
+
+ if (!reqArena) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+
+ if (responses == NULL) /* nothing to do */
+ return SECSuccess;
+
+ for (i = 0; responses[i] != NULL; i++) {
+ SECItem *newStatus;
+ /*
+ * The following assert points out internal errors (problems in
+ * the template definitions or in the ASN.1 decoder itself, etc.).
+ */
+ PORT_Assert(responses[i]->derCertStatus.data != NULL);
+
+ derTag = responses[i]->derCertStatus.data[0] & SEC_ASN1_TAGNUM_MASK;
+ certStatusType = ocsp_CertStatusTypeByTag(derTag);
+ certStatusTemplate = ocsp_CertStatusTemplateByType(certStatusType);
+
+ certStatus = PORT_ArenaZAlloc(reqArena, sizeof(ocspCertStatus));
+ if (certStatus == NULL) {
+ goto loser;
+ }
+ newStatus = SECITEM_ArenaDupItem(reqArena, &responses[i]->derCertStatus);
+ if (!newStatus) {
+ goto loser;
+ }
+ rv = SEC_QuickDERDecodeItem(reqArena, certStatus, certStatusTemplate,
+ newStatus);
+ if (rv != SECSuccess) {
+ if (PORT_GetError() == SEC_ERROR_BAD_DER)
+ PORT_SetError(SEC_ERROR_OCSP_MALFORMED_RESPONSE);
+ goto loser;
+ }
+
+ certStatus->certStatusType = certStatusType;
+ responses[i]->certStatus = certStatus;
+ }
+
+ return SECSuccess;
+
+loser:
+ return rv;
+}
+
+/*
+ * Helper function for decoding a responderID -- turn the actual DER tag
+ * into our local translation.
+ */
+static CERTOCSPResponderIDType
+ocsp_ResponderIDTypeByTag(int derTag)
+{
+ CERTOCSPResponderIDType responderIDType;
+
+ switch (derTag) {
+ case 1:
+ responderIDType = ocspResponderID_byName;
+ break;
+ case 2:
+ responderIDType = ocspResponderID_byKey;
+ break;
+ default:
+ responderIDType = ocspResponderID_other;
+ break;
+ }
+
+ return responderIDType;
+}
+
+/*
+ * Decode "src" as a BasicOCSPResponse, returning the result.
+ */
+static ocspBasicOCSPResponse *
+ocsp_DecodeBasicOCSPResponse(PLArenaPool *arena, SECItem *src)
+{
+ void *mark;
+ ocspBasicOCSPResponse *basicResponse;
+ ocspResponseData *responseData;
+ ocspResponderID *responderID;
+ CERTOCSPResponderIDType responderIDType;
+ const SEC_ASN1Template *responderIDTemplate;
+ int derTag;
+ SECStatus rv;
+ SECItem newsrc;
+
+ mark = PORT_ArenaMark(arena);
+
+ basicResponse = PORT_ArenaZAlloc(arena, sizeof(ocspBasicOCSPResponse));
+ if (basicResponse == NULL) {
+ goto loser;
+ }
+
+ /* copy the DER into the arena, since Quick DER returns data that points
+ into the DER input, which may get freed by the caller */
+ rv = SECITEM_CopyItem(arena, &newsrc, src);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ rv = SEC_QuickDERDecodeItem(arena, basicResponse,
+ ocsp_BasicOCSPResponseTemplate, &newsrc);
+ if (rv != SECSuccess) {
+ if (PORT_GetError() == SEC_ERROR_BAD_DER)
+ PORT_SetError(SEC_ERROR_OCSP_MALFORMED_RESPONSE);
+ goto loser;
+ }
+
+ responseData = basicResponse->tbsResponseData;
+
+ /*
+ * The following asserts point out internal errors (problems in
+ * the template definitions or in the ASN.1 decoder itself, etc.).
+ */
+ PORT_Assert(responseData != NULL);
+ PORT_Assert(responseData->derResponderID.data != NULL);
+
+ /*
+ * XXX Because responderID is a CHOICE, which is not currently handled
+ * by our ASN.1 decoder, we have to decode it "by hand".
+ */
+ derTag = responseData->derResponderID.data[0] & SEC_ASN1_TAGNUM_MASK;
+ responderIDType = ocsp_ResponderIDTypeByTag(derTag);
+ responderIDTemplate = ocsp_ResponderIDTemplateByType(responderIDType);
+
+ responderID = PORT_ArenaZAlloc(arena, sizeof(ocspResponderID));
+ if (responderID == NULL) {
+ goto loser;
+ }
+
+ rv = SEC_QuickDERDecodeItem(arena, responderID, responderIDTemplate,
+ &responseData->derResponderID);
+ if (rv != SECSuccess) {
+ if (PORT_GetError() == SEC_ERROR_BAD_DER)
+ PORT_SetError(SEC_ERROR_OCSP_MALFORMED_RESPONSE);
+ goto loser;
+ }
+
+ responderID->responderIDType = responderIDType;
+ responseData->responderID = responderID;
+
+ /*
+ * XXX Each SingleResponse also contains a CHOICE, which has to be
+ * fixed up by hand.
+ */
+ rv = ocsp_FinishDecodingSingleResponses(arena, responseData->responses);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ PORT_ArenaUnmark(arena, mark);
+ return basicResponse;
+
+loser:
+ PORT_ArenaRelease(arena, mark);
+ return NULL;
+}
+
+/*
+ * Decode the responseBytes based on the responseType found in "rbytes",
+ * leaving the resulting translated/decoded information in there as well.
+ */
+static SECStatus
+ocsp_DecodeResponseBytes(PLArenaPool *arena, ocspResponseBytes *rbytes)
+{
+ if (rbytes == NULL) {
+ PORT_SetError(SEC_ERROR_OCSP_UNKNOWN_RESPONSE_TYPE);
+ return SECFailure;
+ }
+
+ rbytes->responseTypeTag = SECOID_FindOIDTag(&rbytes->responseType);
+ switch (rbytes->responseTypeTag) {
+ case SEC_OID_PKIX_OCSP_BASIC_RESPONSE: {
+ ocspBasicOCSPResponse *basicResponse;
+
+ basicResponse = ocsp_DecodeBasicOCSPResponse(arena,
+ &rbytes->response);
+ if (basicResponse == NULL)
+ return SECFailure;
+
+ rbytes->decodedResponse.basic = basicResponse;
+ } break;
+
+ /*
+ * Add new/future response types here.
+ */
+
+ default:
+ PORT_SetError(SEC_ERROR_OCSP_UNKNOWN_RESPONSE_TYPE);
+ return SECFailure;
+ }
+
+ return SECSuccess;
+}
+
+/*
+ * FUNCTION: CERT_DecodeOCSPResponse
+ * Decode a DER encoded OCSP Response.
+ * INPUTS:
+ * SECItem *src
+ * Pointer to a SECItem holding DER encoded OCSP Response.
+ * RETURN:
+ * Returns a pointer to a CERTOCSPResponse (the decoded OCSP Response);
+ * the caller is responsible for destroying it. Or NULL if error (either
+ * response could not be decoded (SEC_ERROR_OCSP_MALFORMED_RESPONSE),
+ * it was of an unexpected type (SEC_ERROR_OCSP_UNKNOWN_RESPONSE_TYPE),
+ * or a low-level or internal error occurred).
+ */
+CERTOCSPResponse *
+CERT_DecodeOCSPResponse(const SECItem *src)
+{
+ PLArenaPool *arena = NULL;
+ CERTOCSPResponse *response = NULL;
+ SECStatus rv = SECFailure;
+ ocspResponseStatus sv;
+ SECItem newSrc;
+
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (arena == NULL) {
+ goto loser;
+ }
+ response = (CERTOCSPResponse *)PORT_ArenaZAlloc(arena,
+ sizeof(CERTOCSPResponse));
+ if (response == NULL) {
+ goto loser;
+ }
+ response->arena = arena;
+
+ /* copy the DER into the arena, since Quick DER returns data that points
+ into the DER input, which may get freed by the caller */
+ rv = SECITEM_CopyItem(arena, &newSrc, src);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ rv = SEC_QuickDERDecodeItem(arena, response, ocsp_OCSPResponseTemplate, &newSrc);
+ if (rv != SECSuccess) {
+ if (PORT_GetError() == SEC_ERROR_BAD_DER)
+ PORT_SetError(SEC_ERROR_OCSP_MALFORMED_RESPONSE);
+ goto loser;
+ }
+
+ sv = (ocspResponseStatus)DER_GetInteger(&response->responseStatus);
+ response->statusValue = sv;
+ if (sv != ocspResponse_successful) {
+ /*
+ * If the response status is anything but successful, then we
+ * are all done with decoding; the status is all there is.
+ */
+ return response;
+ }
+
+ /*
+ * A successful response contains much more information, still encoded.
+ * Now we need to decode that.
+ */
+ rv = ocsp_DecodeResponseBytes(arena, response->responseBytes);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ return response;
+
+loser:
+ if (arena != NULL) {
+ PORT_FreeArena(arena, PR_FALSE);
+ }
+ return NULL;
+}
+
+/*
+ * The way an OCSPResponse is defined, there are many levels to descend
+ * before getting to the actual response information. And along the way
+ * we need to check that the response *type* is recognizable, which for
+ * now means that it is a BasicOCSPResponse, because that is the only
+ * type currently defined. Rather than force all routines to perform
+ * a bunch of sanity checking every time they want to work on a response,
+ * this function isolates that and gives back the interesting part.
+ * Note that no copying is done, this just returns a pointer into the
+ * substructure of the response which is passed in.
+ *
+ * XXX This routine only works when a valid response structure is passed
+ * into it; this is checked with many assertions. Assuming the response
+ * was creating by decoding, it wouldn't make it this far without being
+ * okay. That is a sufficient assumption since the entire OCSP interface
+ * 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:
+ * Returns a pointer to ocspResponseData structure: decoded OCSP response
+ * data, and a pointer(tbsResponseDataDER) to its undecoded data DER.
+ */
+ocspResponseData *
+ocsp_GetResponseData(CERTOCSPResponse *response, SECItem **tbsResponseDataDER)
+{
+ ocspBasicOCSPResponse *basic;
+ ocspResponseData *responseData;
+
+ PORT_Assert(response != NULL);
+
+ PORT_Assert(response->responseBytes != NULL);
+
+ PORT_Assert(response->responseBytes->responseTypeTag ==
+ SEC_OID_PKIX_OCSP_BASIC_RESPONSE);
+
+ basic = response->responseBytes->decodedResponse.basic;
+ PORT_Assert(basic != NULL);
+
+ responseData = basic->tbsResponseData;
+ PORT_Assert(responseData != NULL);
+
+ if (tbsResponseDataDER) {
+ *tbsResponseDataDER = &basic->tbsResponseDataDER;
+
+ PORT_Assert((*tbsResponseDataDER)->data != NULL);
+ PORT_Assert((*tbsResponseDataDER)->len != 0);
+ }
+
+ return responseData;
+}
+
+/*
+ * Much like the routine above, except it returns the response signature.
+ * Again, no copy is done.
+ */
+ocspSignature *
+ocsp_GetResponseSignature(CERTOCSPResponse *response)
+{
+ ocspBasicOCSPResponse *basic;
+
+ PORT_Assert(response != NULL);
+ if (NULL == response->responseBytes) {
+ return NULL;
+ }
+ if (response->responseBytes->responseTypeTag !=
+ SEC_OID_PKIX_OCSP_BASIC_RESPONSE) {
+ return NULL;
+ }
+ basic = response->responseBytes->decodedResponse.basic;
+ PORT_Assert(basic != NULL);
+
+ return &(basic->responseSignature);
+}
+
+/*
+ * FUNCTION: CERT_DestroyOCSPResponse
+ * Frees an OCSP Response structure.
+ * INPUTS:
+ * CERTOCSPResponse *request
+ * Pointer to CERTOCSPResponse to be freed.
+ * RETURN:
+ * No return value; no errors.
+ */
+void
+CERT_DestroyOCSPResponse(CERTOCSPResponse *response)
+{
+ if (response != NULL) {
+ ocspSignature *signature = ocsp_GetResponseSignature(response);
+ if (signature && signature->cert != NULL)
+ CERT_DestroyCertificate(signature->cert);
+
+ /*
+ * We should actually never have a response without an arena,
+ * but check just in case. (If there isn't one, there is not
+ * much we can do about it...)
+ */
+ PORT_Assert(response->arena != NULL);
+ if (response->arena != NULL) {
+ PORT_FreeArena(response->arena, PR_FALSE);
+ }
+ }
+}
+
+/*
+ * OVERALL OCSP CLIENT SUPPORT (make and send a request, verify a response):
+ */
+
+/*
+ * Pick apart a URL, saving the important things in the passed-in pointers.
+ *
+ * We expect to find "http://<hostname>[:<port>]/[path]", though we will
+ * tolerate that final slash character missing, as well as beginning and
+ * trailing whitespace, and any-case-characters for "http". All of that
+ * tolerance is what complicates this routine. What we want is just to
+ * pick out the hostname, the port, and the path.
+ *
+ * On a successful return, the caller will need to free the output pieces
+ * of hostname and path, which are copies of the values found in the url.
+ */
+static SECStatus
+ocsp_ParseURL(const char *url, char **pHostname, PRUint16 *pPort, char **pPath)
+{
+ unsigned short port = 80; /* default, in case not in url */
+ char *hostname = NULL;
+ char *path = NULL;
+ const char *save;
+ char c;
+ int len;
+
+ if (url == NULL)
+ goto loser;
+
+ /*
+ * Skip beginning whitespace.
+ */
+ c = *url;
+ while ((c == ' ' || c == '\t') && c != '\0') {
+ url++;
+ c = *url;
+ }
+ if (c == '\0')
+ goto loser;
+
+ /*
+ * Confirm, then skip, protocol. (Since we only know how to do http,
+ * that is all we will accept).
+ */
+ if (PORT_Strncasecmp(url, "http://", 7) != 0)
+ goto loser;
+ url += 7;
+
+ /*
+ * Whatever comes next is the hostname (or host IP address). We just
+ * save it aside and then search for its end so we can determine its
+ * length and copy it.
+ *
+ * XXX Note that because we treat a ':' as a terminator character
+ * (and below, we expect that to mean there is a port specification
+ * immediately following), we will not handle IPv6 addresses. That is
+ * apparently an acceptable limitation, for the time being. Some day,
+ * when there is a clear way to specify a URL with an IPv6 address that
+ * can be parsed unambiguously, this code should be made to do that.
+ */
+ save = url;
+ c = *url;
+ while (c != '/' && c != ':' && c != '\0' && c != ' ' && c != '\t') {
+ url++;
+ c = *url;
+ }
+ len = url - save;
+ hostname = PORT_Alloc(len + 1);
+ if (hostname == NULL)
+ goto loser;
+ PORT_Memcpy(hostname, save, len);
+ hostname[len] = '\0';
+
+ /*
+ * Now we figure out if there was a port specified or not.
+ * If so, we need to parse it (as a number) and skip it.
+ */
+ if (c == ':') {
+ url++;
+ port = (unsigned short)PORT_Atoi(url);
+ c = *url;
+ while (c != '/' && c != '\0' && c != ' ' && c != '\t') {
+ if (c < '0' || c > '9')
+ goto loser;
+ url++;
+ c = *url;
+ }
+ }
+
+ /*
+ * Last thing to find is a path. There *should* be a slash,
+ * if nothing else -- but if there is not we provide one.
+ */
+ if (c == '/') {
+ save = url;
+ while (c != '\0' && c != ' ' && c != '\t') {
+ url++;
+ c = *url;
+ }
+ len = url - save;
+ path = PORT_Alloc(len + 1);
+ if (path == NULL)
+ goto loser;
+ PORT_Memcpy(path, save, len);
+ path[len] = '\0';
+ } else {
+ path = PORT_Strdup("/");
+ if (path == NULL)
+ goto loser;
+ }
+
+ *pHostname = hostname;
+ *pPort = port;
+ *pPath = path;
+ return SECSuccess;
+
+loser:
+ if (hostname != NULL)
+ PORT_Free(hostname);
+ PORT_SetError(SEC_ERROR_CERT_BAD_ACCESS_LOCATION);
+ return SECFailure;
+}
+
+/*
+ * Open a socket to the specified host on the specified port, and return it.
+ * The host is either a hostname or an IP address.
+ */
+static PRFileDesc *
+ocsp_ConnectToHost(const char *host, PRUint16 port)
+{
+ PRFileDesc *sock = NULL;
+ PRIntervalTime timeout;
+ PRNetAddr addr;
+ char *netdbbuf = NULL;
+
+ sock = PR_NewTCPSocket();
+ if (sock == NULL)
+ goto loser;
+
+ /* XXX Some day need a way to set (and get?) the following value */
+ timeout = PR_SecondsToInterval(30);
+
+ /*
+ * If the following converts an IP address string in "dot notation"
+ * into a PRNetAddr. If it fails, we assume that is because we do not
+ * have such an address, but instead a host *name*. In that case we
+ * then lookup the host by name. Using the NSPR function this way
+ * means we do not have to have our own logic for distinguishing a
+ * valid numerical IP address from a hostname.
+ */
+ if (PR_StringToNetAddr(host, &addr) != PR_SUCCESS) {
+ PRIntn hostIndex;
+ PRHostEnt hostEntry;
+
+ netdbbuf = PORT_Alloc(PR_NETDB_BUF_SIZE);
+ if (netdbbuf == NULL)
+ goto loser;
+
+ if (PR_GetHostByName(host, netdbbuf, PR_NETDB_BUF_SIZE,
+ &hostEntry) != PR_SUCCESS)
+ goto loser;
+
+ hostIndex = 0;
+ do {
+ hostIndex = PR_EnumerateHostEnt(hostIndex, &hostEntry, port, &addr);
+ if (hostIndex <= 0)
+ goto loser;
+ } while (PR_Connect(sock, &addr, timeout) != PR_SUCCESS);
+
+ PORT_Free(netdbbuf);
+ } else {
+ /*
+ * First put the port into the address, then connect.
+ */
+ if (PR_InitializeNetAddr(PR_IpAddrNull, port, &addr) != PR_SUCCESS)
+ goto loser;
+ if (PR_Connect(sock, &addr, timeout) != PR_SUCCESS)
+ goto loser;
+ }
+
+ return sock;
+
+loser:
+ if (sock != NULL)
+ PR_Close(sock);
+ if (netdbbuf != NULL)
+ PORT_Free(netdbbuf);
+ return NULL;
+}
+
+/*
+ * Sends an encoded OCSP request to the server identified by "location",
+ * and returns the socket on which it was sent (so can listen for the reply).
+ * "location" is expected to be a valid URL -- an error parsing it produces
+ * SEC_ERROR_CERT_BAD_ACCESS_LOCATION. Other errors are likely problems
+ * connecting to it, or writing to it, or allocating memory, and the low-level
+ * errors appropriate to the problem will be set.
+ * if (encodedRequest == NULL)
+ * then location MUST already include the full request,
+ * including base64 and urlencode,
+ * and the request will be sent with GET
+ * if (encodedRequest != NULL)
+ * then the request will be sent with POST
+ */
+static PRFileDesc *
+ocsp_SendEncodedRequest(const char *location, const SECItem *encodedRequest)
+{
+ char *hostname = NULL;
+ char *path = NULL;
+ PRUint16 port;
+ SECStatus rv;
+ PRFileDesc *sock = NULL;
+ PRFileDesc *returnSock = NULL;
+ char *header = NULL;
+ char portstr[16];
+
+ /*
+ * Take apart the location, getting the hostname, port, and path.
+ */
+ rv = ocsp_ParseURL(location, &hostname, &port, &path);
+ if (rv != SECSuccess)
+ goto loser;
+
+ PORT_Assert(hostname != NULL);
+ PORT_Assert(path != NULL);
+
+ sock = ocsp_ConnectToHost(hostname, port);
+ if (sock == NULL)
+ goto loser;
+
+ portstr[0] = '\0';
+ if (port != 80) {
+ PR_snprintf(portstr, sizeof(portstr), ":%d", port);
+ }
+
+ if (!encodedRequest) {
+ header = PR_smprintf("GET %s HTTP/1.0\r\n"
+ "Host: %s%s\r\n\r\n",
+ path, hostname, portstr);
+ if (header == NULL)
+ goto loser;
+
+ /*
+ * The NSPR documentation promises that if it can, it will write the full
+ * amount; this will not return a partial value expecting us to loop.
+ */
+ if (PR_Write(sock, header, (PRInt32)PORT_Strlen(header)) < 0)
+ goto loser;
+ } else {
+ header = PR_smprintf("POST %s HTTP/1.0\r\n"
+ "Host: %s%s\r\n"
+ "Content-Type: application/ocsp-request\r\n"
+ "Content-Length: %u\r\n\r\n",
+ path, hostname, portstr, encodedRequest->len);
+ if (header == NULL)
+ goto loser;
+
+ /*
+ * The NSPR documentation promises that if it can, it will write the full
+ * amount; this will not return a partial value expecting us to loop.
+ */
+ if (PR_Write(sock, header, (PRInt32)PORT_Strlen(header)) < 0)
+ goto loser;
+
+ if (PR_Write(sock, encodedRequest->data,
+ (PRInt32)encodedRequest->len) < 0)
+ goto loser;
+ }
+
+ returnSock = sock;
+ sock = NULL;
+
+loser:
+ if (header != NULL)
+ PORT_Free(header);
+ if (sock != NULL)
+ PR_Close(sock);
+ if (path != NULL)
+ PORT_Free(path);
+ if (hostname != NULL)
+ PORT_Free(hostname);
+
+ return returnSock;
+}
+
+/*
+ * Read from "fd" into "buf" -- expect/attempt to read a given number of bytes
+ * Obviously, stop if hit end-of-stream. Timeout is passed in.
+ */
+
+static int
+ocsp_read(PRFileDesc *fd, char *buf, int toread, PRIntervalTime timeout)
+{
+ int total = 0;
+
+ while (total < toread) {
+ PRInt32 got;
+
+ got = PR_Recv(fd, buf + total, (PRInt32)(toread - total), 0, timeout);
+ if (got < 0) {
+ if (0 == total) {
+ total = -1; /* report the error if we didn't read anything yet */
+ }
+ break;
+ } else if (got == 0) { /* EOS */
+ break;
+ }
+
+ total += got;
+ }
+
+ return total;
+}
+
+#define OCSP_BUFSIZE 1024
+
+#define AbortHttpDecode(error) \
+ { \
+ if (inBuffer) \
+ PORT_Free(inBuffer); \
+ PORT_SetError(error); \
+ return NULL; \
+ }
+
+/*
+ * Reads on the given socket and returns an encoded response when received.
+ * Properly formatted HTTP/1.0 response headers are expected to be read
+ * from the socket, preceding a binary-encoded OCSP response. Problems
+ * with parsing cause the error SEC_ERROR_OCSP_BAD_HTTP_RESPONSE to be
+ * set; any other problems are likely low-level i/o or memory allocation
+ * errors.
+ */
+static SECItem *
+ocsp_GetEncodedResponse(PLArenaPool *arena, PRFileDesc *sock)
+{
+ /* first read HTTP status line and headers */
+
+ char *inBuffer = NULL;
+ PRInt32 offset = 0;
+ PRInt32 inBufsize = 0;
+ const PRInt32 bufSizeIncrement = OCSP_BUFSIZE; /* 1 KB at a time */
+ const PRInt32 maxBufSize = 8 * bufSizeIncrement; /* 8 KB max */
+ const char *CRLF = "\r\n";
+ const PRInt32 CRLFlen = strlen(CRLF);
+ const char *headerEndMark = "\r\n\r\n";
+ const PRInt32 markLen = strlen(headerEndMark);
+ const PRIntervalTime ocsptimeout =
+ PR_SecondsToInterval(30); /* hardcoded to 30s for now */
+ char *headerEnd = NULL;
+ PRBool EOS = PR_FALSE;
+ const char *httpprotocol = "HTTP/";
+ const PRInt32 httplen = strlen(httpprotocol);
+ const char *httpcode = NULL;
+ const char *contenttype = NULL;
+ PRInt32 contentlength = 0;
+ PRInt32 bytesRead = 0;
+ char *statusLineEnd = NULL;
+ char *space = NULL;
+ char *nextHeader = NULL;
+ SECItem *result = NULL;
+
+ /* read up to at least the end of the HTTP headers */
+ do {
+ inBufsize += bufSizeIncrement;
+ inBuffer = PORT_Realloc(inBuffer, inBufsize + 1);
+ if (NULL == inBuffer) {
+ AbortHttpDecode(SEC_ERROR_NO_MEMORY);
+ }
+ bytesRead = ocsp_read(sock, inBuffer + offset, bufSizeIncrement,
+ ocsptimeout);
+ if (bytesRead > 0) {
+ PRInt32 searchOffset = (offset - markLen) > 0 ? offset - markLen : 0;
+ offset += bytesRead;
+ *(inBuffer + offset) = '\0'; /* NULL termination */
+ headerEnd = strstr((const char *)inBuffer + searchOffset, headerEndMark);
+ if (bytesRead < bufSizeIncrement) {
+ /* we read less data than requested, therefore we are at
+ EOS or there was a read error */
+ EOS = PR_TRUE;
+ }
+ } else {
+ /* recv error or EOS */
+ EOS = PR_TRUE;
+ }
+ } while ((!headerEnd) && (PR_FALSE == EOS) &&
+ (inBufsize < maxBufSize));
+
+ if (!headerEnd) {
+ AbortHttpDecode(SEC_ERROR_OCSP_BAD_HTTP_RESPONSE);
+ }
+
+ /* parse the HTTP status line */
+ statusLineEnd = strstr((const char *)inBuffer, CRLF);
+ if (!statusLineEnd) {
+ AbortHttpDecode(SEC_ERROR_OCSP_BAD_HTTP_RESPONSE);
+ }
+ *statusLineEnd = '\0';
+
+ /* check for HTTP/ response */
+ space = strchr((const char *)inBuffer, ' ');
+ if (!space || PORT_Strncasecmp((const char *)inBuffer, httpprotocol, httplen) != 0) {
+ AbortHttpDecode(SEC_ERROR_OCSP_BAD_HTTP_RESPONSE);
+ }
+
+ /* check the HTTP status code of 200 */
+ httpcode = space + 1;
+ space = strchr(httpcode, ' ');
+ if (!space) {
+ AbortHttpDecode(SEC_ERROR_OCSP_BAD_HTTP_RESPONSE);
+ }
+ *space = 0;
+ if (0 != strcmp(httpcode, "200")) {
+ AbortHttpDecode(SEC_ERROR_OCSP_BAD_HTTP_RESPONSE);
+ }
+
+ /* parse the HTTP headers in the buffer . We only care about
+ content-type and content-length
+ */
+
+ nextHeader = statusLineEnd + CRLFlen;
+ *headerEnd = '\0'; /* terminate */
+ do {
+ char *thisHeaderEnd = NULL;
+ char *value = NULL;
+ char *colon = strchr(nextHeader, ':');
+
+ if (!colon) {
+ AbortHttpDecode(SEC_ERROR_OCSP_BAD_HTTP_RESPONSE);
+ }
+
+ *colon = '\0';
+ value = colon + 1;
+
+ /* jpierre - note : the following code will only handle the basic form
+ of HTTP/1.0 response headers, of the form "name: value" . Headers
+ split among multiple lines are not supported. This is not common
+ and should not be an issue, but it could become one in the
+ future */
+
+ if (*value != ' ') {
+ AbortHttpDecode(SEC_ERROR_OCSP_BAD_HTTP_RESPONSE);
+ }
+
+ value++;
+ thisHeaderEnd = strstr(value, CRLF);
+ if (thisHeaderEnd) {
+ *thisHeaderEnd = '\0';
+ }
+
+ if (0 == PORT_Strcasecmp(nextHeader, "content-type")) {
+ contenttype = value;
+ } else if (0 == PORT_Strcasecmp(nextHeader, "content-length")) {
+ contentlength = atoi(value);
+ }
+
+ if (thisHeaderEnd) {
+ nextHeader = thisHeaderEnd + CRLFlen;
+ } else {
+ nextHeader = NULL;
+ }
+
+ } while (nextHeader && (nextHeader < (headerEnd + CRLFlen)));
+
+ /* check content-type */
+ if (!contenttype ||
+ (0 != PORT_Strcasecmp(contenttype, "application/ocsp-response"))) {
+ AbortHttpDecode(SEC_ERROR_OCSP_BAD_HTTP_RESPONSE);
+ }
+
+ /* read the body of the OCSP response */
+ offset = offset - (PRInt32)(headerEnd - (const char *)inBuffer) - markLen;
+ if (offset) {
+ /* move all data to the beginning of the buffer */
+ PORT_Memmove(inBuffer, headerEnd + markLen, offset);
+ }
+
+ /* resize buffer to only what's needed to hold the current response */
+ inBufsize = (1 + (offset - 1) / bufSizeIncrement) * bufSizeIncrement;
+
+ while ((PR_FALSE == EOS) &&
+ ((contentlength == 0) || (offset < contentlength)) &&
+ (inBufsize < maxBufSize)) {
+ /* we still need to receive more body data */
+ inBufsize += bufSizeIncrement;
+ inBuffer = PORT_Realloc(inBuffer, inBufsize + 1);
+ if (NULL == inBuffer) {
+ AbortHttpDecode(SEC_ERROR_NO_MEMORY);
+ }
+ bytesRead = ocsp_read(sock, inBuffer + offset, bufSizeIncrement,
+ ocsptimeout);
+ if (bytesRead > 0) {
+ offset += bytesRead;
+ if (bytesRead < bufSizeIncrement) {
+ /* we read less data than requested, therefore we are at
+ EOS or there was a read error */
+ EOS = PR_TRUE;
+ }
+ } else {
+ /* recv error or EOS */
+ EOS = PR_TRUE;
+ }
+ }
+
+ if (0 == offset) {
+ AbortHttpDecode(SEC_ERROR_OCSP_BAD_HTTP_RESPONSE);
+ }
+
+ /*
+ * Now allocate the item to hold the data.
+ */
+ result = SECITEM_AllocItem(arena, NULL, offset);
+ if (NULL == result) {
+ AbortHttpDecode(SEC_ERROR_NO_MEMORY);
+ }
+
+ /*
+ * And copy the data left in the buffer.
+ */
+ PORT_Memcpy(result->data, inBuffer, offset);
+
+ /* and free the temporary buffer */
+ PORT_Free(inBuffer);
+ return result;
+}
+
+SECStatus
+CERT_ParseURL(const char *url, char **pHostname, PRUint16 *pPort, char **pPath)
+{
+ return ocsp_ParseURL(url, pHostname, pPort, pPath);
+}
+
+/*
+ * Limit the size of http responses we are willing to accept.
+ */
+#define MAX_WANTED_OCSP_RESPONSE_LEN 64 * 1024
+
+/* if (encodedRequest == NULL)
+ * then location MUST already include the full request,
+ * including base64 and urlencode,
+ * and the request will be sent with GET
+ * if (encodedRequest != NULL)
+ * then the request will be sent with POST
+ */
+static SECItem *
+fetchOcspHttpClientV1(PLArenaPool *arena,
+ const SEC_HttpClientFcnV1 *hcv1,
+ const char *location,
+ const 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
+ No lock for accessing OCSP_Global.timeoutSeconds, bug 406120
+ */
+
+ if ((*hcv1->createFcn)(
+ pServerSession,
+ "http",
+ path,
+ encodedRequest ? "POST" : "GET",
+ PR_TicksPerSecond() * OCSP_Global.timeoutSeconds,
+ &pRequestSession) != SECSuccess) {
+ PORT_SetError(SEC_ERROR_OCSP_SERVER_ERROR);
+ goto loser;
+ }
+
+ if (encodedRequest &&
+ (*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;
+
+ OCSP_TRACE(("OCSP trySendAndReceive %s\n", location));
+
+ if ((*hcv1->trySendAndReceiveFcn)(
+ pRequestSession,
+ NULL,
+ &myHttpResponseCode,
+ NULL,
+ NULL,
+ &myHttpResponseData,
+ &myHttpResponseDataLen) != SECSuccess) {
+ PORT_SetError(SEC_ERROR_OCSP_SERVER_ERROR);
+ goto loser;
+ }
+
+ OCSP_TRACE(("OCSP trySendAndReceive result http %d\n", myHttpResponseCode));
+
+ 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_GetEncodedOCSPResponseByMethod
+ * Creates and sends a request to an OCSP responder, then reads and
+ * returns the (encoded) response.
+ * INPUTS:
+ * PLArenaPool *arena
+ * Pointer to arena from which return value will be allocated.
+ * If NULL, result will be allocated from the heap (and thus should
+ * be freed via SECITEM_FreeItem).
+ * CERTCertList *certList
+ * A list of certs for which status will be requested.
+ * Note that all of these certificates should have the same issuer,
+ * or it's expected the response will be signed by a trusted responder.
+ * If the certs need to be broken up into multiple requests, that
+ * must be handled by the caller (and thus by having multiple calls
+ * to this routine), who knows about where the request(s) are being
+ * sent and whether there are any trusted responders in place.
+ * const char *location
+ * The location of the OCSP responder (a URL).
+ * const char *method
+ * The protocol method used when retrieving the OCSP response.
+ * Currently support: "GET" (http GET) and "POST" (http POST).
+ * Additionals methods for http or other protocols might be added
+ * in the future.
+ * PRTime time
+ * Indicates the time for which the certificate status is to be
+ * determined -- this may be used in the search for the cert's issuer
+ * but has no other bearing on the operation.
+ * PRBool addServiceLocator
+ * If true, the Service Locator extension should be added to the
+ * single request(s) for each cert.
+ * CERTCertificate *signerCert
+ * If non-NULL, means sign the request using this cert. Otherwise,
+ * do not sign.
+ * void *pwArg
+ * Pointer to argument for password prompting, if needed. (Definitely
+ * not needed if not signing.)
+ * OUTPUTS:
+ * CERTOCSPRequest **pRequest
+ * Pointer in which to store the OCSP request created for the given
+ * list of certificates. It is only filled in if the entire operation
+ * is successful and the pointer is not null -- and in that case the
+ * caller is then reponsible for destroying it.
+ * RETURN:
+ * Returns a pointer to the SECItem holding the response.
+ * On error, returns null with error set describing the reason:
+ * SEC_ERROR_UNKNOWN_ISSUER
+ * SEC_ERROR_CERT_BAD_ACCESS_LOCATION
+ * SEC_ERROR_OCSP_BAD_HTTP_RESPONSE
+ * Other errors are low-level problems (no memory, bad database, etc.).
+ */
+SECItem *
+CERT_GetEncodedOCSPResponseByMethod(PLArenaPool *arena, CERTCertList *certList,
+ const char *location, const char *method,
+ PRTime time, PRBool addServiceLocator,
+ CERTCertificate *signerCert, void *pwArg,
+ CERTOCSPRequest **pRequest)
+{
+ CERTOCSPRequest *request;
+ request = CERT_CreateOCSPRequest(certList, time, addServiceLocator,
+ signerCert);
+ if (!request)
+ return NULL;
+ return ocsp_GetEncodedOCSPResponseFromRequest(arena, request, location,
+ method, time, addServiceLocator,
+ pwArg, pRequest);
+}
+
+/*
+ * FUNCTION: CERT_GetEncodedOCSPResponse
+ * Creates and sends a request to an OCSP responder, then reads and
+ * returns the (encoded) response.
+ *
+ * This is a legacy API that behaves identically to
+ * CERT_GetEncodedOCSPResponseByMethod using the "POST" method.
+ */
+SECItem *
+CERT_GetEncodedOCSPResponse(PLArenaPool *arena, CERTCertList *certList,
+ const char *location, PRTime time,
+ PRBool addServiceLocator,
+ CERTCertificate *signerCert, void *pwArg,
+ CERTOCSPRequest **pRequest)
+{
+ return CERT_GetEncodedOCSPResponseByMethod(arena, certList, location,
+ "POST", time, addServiceLocator,
+ signerCert, pwArg, pRequest);
+}
+
+/* URL encode a buffer that consists of base64-characters, only,
+ * which means we can use a simple encoding logic.
+ *
+ * No output buffer size checking is performed.
+ * You should call the function twice, to calculate the required buffer size.
+ *
+ * If the outpufBuf parameter is NULL, the function will calculate the
+ * required size, including the trailing zero termination char.
+ *
+ * The function returns the number of bytes calculated or produced.
+ */
+size_t
+ocsp_UrlEncodeBase64Buf(const char *base64Buf, char *outputBuf)
+{
+ const char *walkInput = NULL;
+ char *walkOutput = outputBuf;
+ size_t count = 0;
+
+ for (walkInput = base64Buf; *walkInput; ++walkInput) {
+ char c = *walkInput;
+ if (isspace(c))
+ continue;
+ switch (c) {
+ case '+':
+ if (outputBuf) {
+ strcpy(walkOutput, "%2B");
+ walkOutput += 3;
+ }
+ count += 3;
+ break;
+ case '/':
+ if (outputBuf) {
+ strcpy(walkOutput, "%2F");
+ walkOutput += 3;
+ }
+ count += 3;
+ break;
+ case '=':
+ if (outputBuf) {
+ strcpy(walkOutput, "%3D");
+ walkOutput += 3;
+ }
+ count += 3;
+ break;
+ default:
+ if (outputBuf) {
+ *walkOutput = *walkInput;
+ ++walkOutput;
+ }
+ ++count;
+ break;
+ }
+ }
+ if (outputBuf) {
+ *walkOutput = 0;
+ }
+ ++count;
+ return count;
+}
+
+enum { max_get_request_size = 255 }; /* defined by RFC2560 */
+
+static SECItem *
+cert_GetOCSPResponse(PLArenaPool *arena, const char *location,
+ const SECItem *encodedRequest);
+
+static SECItem *
+ocsp_GetEncodedOCSPResponseFromRequest(PLArenaPool *arena,
+ CERTOCSPRequest *request,
+ const char *location,
+ const char *method,
+ PRTime time,
+ PRBool addServiceLocator,
+ void *pwArg,
+ CERTOCSPRequest **pRequest)
+{
+ SECItem *encodedRequest = NULL;
+ SECItem *encodedResponse = NULL;
+ SECStatus rv;
+
+ if (!location || !*location) /* location should be at least one byte */
+ goto loser;
+
+ rv = CERT_AddOCSPAcceptableResponses(request,
+ SEC_OID_PKIX_OCSP_BASIC_RESPONSE);
+ if (rv != SECSuccess)
+ goto loser;
+
+ encodedRequest = CERT_EncodeOCSPRequest(NULL, request, pwArg);
+ if (encodedRequest == NULL)
+ goto loser;
+
+ if (!strcmp(method, "GET")) {
+ encodedResponse = cert_GetOCSPResponse(arena, location, encodedRequest);
+ } else if (!strcmp(method, "POST")) {
+ encodedResponse = CERT_PostOCSPRequest(arena, location, encodedRequest);
+ } else {
+ goto loser;
+ }
+
+ if (encodedResponse != NULL && pRequest != NULL) {
+ *pRequest = request;
+ request = NULL; /* avoid destroying below */
+ }
+
+loser:
+ if (request != NULL)
+ CERT_DestroyOCSPRequest(request);
+ if (encodedRequest != NULL)
+ SECITEM_FreeItem(encodedRequest, PR_TRUE);
+ return encodedResponse;
+}
+
+static SECItem *
+cert_FetchOCSPResponse(PLArenaPool *arena, const char *location,
+ const SECItem *encodedRequest);
+
+/* using HTTP GET method */
+static SECItem *
+cert_GetOCSPResponse(PLArenaPool *arena, const char *location,
+ const SECItem *encodedRequest)
+{
+ char *walkOutput = NULL;
+ char *fullGetPath = NULL;
+ size_t pathLength;
+ PRInt32 urlEncodedBufLength;
+ size_t base64size;
+ char b64ReqBuf[max_get_request_size + 1];
+ size_t slashLengthIfNeeded = 0;
+ size_t getURLLength;
+ SECItem *item;
+
+ if (!location || !*location) {
+ return NULL;
+ }
+
+ pathLength = strlen(location);
+ if (location[pathLength - 1] != '/') {
+ slashLengthIfNeeded = 1;
+ }
+
+ /* Calculation as documented by PL_Base64Encode function.
+ * Use integer conversion to avoid having to use function ceil().
+ */
+ base64size = (((encodedRequest->len + 2) / 3) * 4);
+ if (base64size > max_get_request_size) {
+ return NULL;
+ }
+ memset(b64ReqBuf, 0, sizeof(b64ReqBuf));
+ PL_Base64Encode((const char *)encodedRequest->data, encodedRequest->len,
+ b64ReqBuf);
+
+ urlEncodedBufLength = ocsp_UrlEncodeBase64Buf(b64ReqBuf, NULL);
+ getURLLength = pathLength + urlEncodedBufLength + slashLengthIfNeeded;
+
+ /* urlEncodedBufLength already contains room for the zero terminator.
+ * Add another if we must add the '/' char.
+ */
+ if (arena) {
+ fullGetPath = (char *)PORT_ArenaAlloc(arena, getURLLength);
+ } else {
+ fullGetPath = (char *)PORT_Alloc(getURLLength);
+ }
+ if (!fullGetPath) {
+ return NULL;
+ }
+
+ strcpy(fullGetPath, location);
+ walkOutput = fullGetPath + pathLength;
+
+ if (walkOutput > fullGetPath && slashLengthIfNeeded) {
+ strcpy(walkOutput, "/");
+ ++walkOutput;
+ }
+ ocsp_UrlEncodeBase64Buf(b64ReqBuf, walkOutput);
+
+ item = cert_FetchOCSPResponse(arena, fullGetPath, NULL);
+ if (!arena) {
+ PORT_Free(fullGetPath);
+ }
+ return item;
+}
+
+SECItem *
+CERT_PostOCSPRequest(PLArenaPool *arena, const char *location,
+ const SECItem *encodedRequest)
+{
+ return cert_FetchOCSPResponse(arena, location, encodedRequest);
+}
+
+SECItem *
+cert_FetchOCSPResponse(PLArenaPool *arena, const char *location,
+ const SECItem *encodedRequest)
+{
+ const SEC_HttpClientFcn *registeredHttpClient;
+ SECItem *encodedResponse = NULL;
+
+ registeredHttpClient = SEC_GetRegisteredHttpClient();
+
+ if (registeredHttpClient && registeredHttpClient->version == 1) {
+ encodedResponse = fetchOcspHttpClientV1(
+ arena,
+ &registeredHttpClient->fcnTable.ftable1,
+ location,
+ encodedRequest);
+ } else {
+ /* use internal http client */
+ PRFileDesc *sock = ocsp_SendEncodedRequest(location, encodedRequest);
+ if (sock) {
+ encodedResponse = ocsp_GetEncodedResponse(arena, sock);
+ PR_Close(sock);
+ }
+ }
+
+ return encodedResponse;
+}
+
+static SECItem *
+ocsp_GetEncodedOCSPResponseForSingleCert(PLArenaPool *arena,
+ CERTOCSPCertID *certID,
+ CERTCertificate *singleCert,
+ const char *location,
+ const char *method,
+ PRTime time,
+ PRBool addServiceLocator,
+ void *pwArg,
+ CERTOCSPRequest **pRequest)
+{
+ CERTOCSPRequest *request;
+ request = cert_CreateSingleCertOCSPRequest(certID, singleCert, time,
+ addServiceLocator, NULL);
+ if (!request)
+ return NULL;
+ return ocsp_GetEncodedOCSPResponseFromRequest(arena, request, location,
+ method, time, addServiceLocator,
+ pwArg, pRequest);
+}
+
+/* Checks a certificate for the key usage extension of OCSP signer. */
+static PRBool
+ocsp_CertIsOCSPDesignatedResponder(CERTCertificate *cert)
+{
+ SECStatus rv;
+ SECItem extItem;
+ SECItem **oids;
+ SECItem *oid;
+ SECOidTag oidTag;
+ PRBool retval;
+ CERTOidSequence *oidSeq = NULL;
+
+ extItem.data = NULL;
+ rv = CERT_FindCertExtension(cert, SEC_OID_X509_EXT_KEY_USAGE, &extItem);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ oidSeq = CERT_DecodeOidSequence(&extItem);
+ if (oidSeq == NULL) {
+ goto loser;
+ }
+
+ oids = oidSeq->oids;
+ while (*oids != NULL) {
+ oid = *oids;
+
+ oidTag = SECOID_FindOIDTag(oid);
+
+ if (oidTag == SEC_OID_OCSP_RESPONDER) {
+ goto success;
+ }
+
+ oids++;
+ }
+
+loser:
+ retval = PR_FALSE;
+ PORT_SetError(SEC_ERROR_OCSP_INVALID_SIGNING_CERT);
+ goto done;
+success:
+ retval = PR_TRUE;
+done:
+ if (extItem.data != NULL) {
+ PORT_Free(extItem.data);
+ }
+ if (oidSeq != NULL) {
+ CERT_DestroyOidSequence(oidSeq);
+ }
+
+ return (retval);
+}
+
+#ifdef LATER /* \
+ * XXX This function is not currently used, but will \
+ * be needed later when we do revocation checking of \
+ * the responder certificate. Of course, it may need \
+ * revising then, if the cert extension interface has \
+ * changed. (Hopefully it will!) \
+ */
+
+/* Checks a certificate to see if it has the OCSP no check extension. */
+static PRBool
+ocsp_CertHasNoCheckExtension(CERTCertificate *cert)
+{
+ SECStatus rv;
+
+ rv = CERT_FindCertExtension(cert, SEC_OID_PKIX_OCSP_NO_CHECK,
+ NULL);
+ if (rv == SECSuccess) {
+ return PR_TRUE;
+ }
+ return PR_FALSE;
+}
+#endif /* LATER */
+
+static PRBool
+ocsp_matchcert(SECItem *certIndex, CERTCertificate *testCert)
+{
+ SECItem item;
+ unsigned char buf[HASH_LENGTH_MAX];
+
+ item.data = buf;
+ item.len = SHA1_LENGTH;
+
+ if (CERT_GetSubjectPublicKeyDigest(NULL, testCert, SEC_OID_SHA1,
+ &item) == NULL) {
+ return PR_FALSE;
+ }
+ if (SECITEM_ItemsAreEqual(certIndex, &item)) {
+ return PR_TRUE;
+ }
+ if (CERT_GetSubjectPublicKeyDigest(NULL, testCert, SEC_OID_MD5,
+ &item) == NULL) {
+ return PR_FALSE;
+ }
+ if (SECITEM_ItemsAreEqual(certIndex, &item)) {
+ return PR_TRUE;
+ }
+ if (CERT_GetSubjectPublicKeyDigest(NULL, testCert, SEC_OID_MD2,
+ &item) == NULL) {
+ return PR_FALSE;
+ }
+ if (SECITEM_ItemsAreEqual(certIndex, &item)) {
+ return PR_TRUE;
+ }
+
+ return PR_FALSE;
+}
+
+static CERTCertificate *
+ocsp_CertGetDefaultResponder(CERTCertDBHandle *handle, CERTOCSPCertID *certID);
+
+CERTCertificate *
+ocsp_GetSignerCertificate(CERTCertDBHandle *handle, ocspResponseData *tbsData,
+ ocspSignature *signature, CERTCertificate *issuer)
+{
+ CERTCertificate **certs = NULL;
+ CERTCertificate *signerCert = NULL;
+ SECStatus rv = SECFailure;
+ PRBool lookupByName = PR_TRUE;
+ void *certIndex = NULL;
+ int certCount = 0;
+
+ 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 NULL;
+ }
+
+ /*
+ * If the signature contains some certificates as well, temporarily
+ * import them in case they are needed for verification.
+ *
+ * Note that the result of this is that each cert in "certs" needs
+ * to be destroyed.
+ */
+ if (signature->derCerts != NULL) {
+ for (; signature->derCerts[certCount] != NULL; certCount++) {
+ /* just counting */
+ }
+ rv = CERT_ImportCerts(handle, certUsageStatusResponder, 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 *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;
+ CERTCertificate *responder =
+ ocsp_CertGetDefaultResponder(handle, NULL);
+ if (responder && ocsp_matchcert(certIndex, responder)) {
+ signerCert = CERT_DupCertificate(responder);
+ } else if (issuer && ocsp_matchcert(certIndex, issuer)) {
+ signerCert = CERT_DupCertificate(issuer);
+ }
+ for (i = 0; (signerCert == NULL) && (i < certCount); i++) {
+ if (ocsp_matchcert(certIndex, certs[i])) {
+ signerCert = CERT_DupCertificate(certs[i]);
+ }
+ }
+ if (signerCert == NULL) {
+ PORT_SetError(SEC_ERROR_UNKNOWN_CERT);
+ }
+ }
+
+finish:
+ if (certs != NULL) {
+ CERT_DestroyCertArray(certs, certCount);
+ }
+
+ return signerCert;
+}
+
+SECStatus
+ocsp_VerifyResponseSignature(CERTCertificate *signerCert,
+ ocspSignature *signature,
+ SECItem *tbsResponseDataDER,
+ void *pwArg)
+{
+ SECKEYPublicKey *signerKey = NULL;
+ SECStatus rv = SECFailure;
+ CERTSignedData signedData;
+
+ /*
+ * Now get the public key from the signer's certificate; we need
+ * it to perform the verification.
+ */
+ signerKey = CERT_ExtractPublicKey(signerCert);
+ if (signerKey == NULL) {
+ return SECFailure;
+ }
+
+ /*
+ * We copy the signature data *pointer* and length, so that we can
+ * modify the length without damaging the original copy. This is a
+ * simple copy, not a dup, so no destroy/free is necessary.
+ */
+ signedData.signature = signature->signature;
+ signedData.signatureAlgorithm = signature->signatureAlgorithm;
+ signedData.data = *tbsResponseDataDER;
+
+ rv = CERT_VerifySignedDataWithPublicKey(&signedData, signerKey, pwArg);
+ if (rv != SECSuccess &&
+ (PORT_GetError() == SEC_ERROR_BAD_SIGNATURE ||
+ PORT_GetError() == SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED)) {
+ PORT_SetError(SEC_ERROR_OCSP_BAD_SIGNATURE);
+ }
+
+ if (signerKey != NULL) {
+ SECKEY_DestroyPublicKey(signerKey);
+ }
+
+ 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)
+{
+ SECItem *tbsResponseDataDER;
+ CERTCertificate *signerCert = NULL;
+ SECStatus rv = SECFailure;
+ PRTime 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
+ * return the cached result.
+ */
+ if (signature->wasChecked) {
+ if (signature->status == SECSuccess) {
+ if (pSignerCert != NULL)
+ *pSignerCert = CERT_DupCertificate(signature->cert);
+ } else {
+ PORT_SetError(signature->failureReason);
+ }
+ return signature->status;
+ }
+
+ signerCert = ocsp_GetSignerCertificate(handle, tbsData,
+ signature, issuer);
+ if (signerCert == NULL) {
+ rv = SECFailure;
+ if (PORT_GetError() == SEC_ERROR_UNKNOWN_CERT) {
+ /* Make the error a little more specific. */
+ PORT_SetError(SEC_ERROR_OCSP_INVALID_SIGNING_CERT);
+ }
+ goto finish;
+ }
+
+ /*
+ * We could mark this true at the top of this function, or always
+ * below at "finish", but if the problem was just that we could not
+ * find the signer's cert, leave that as if the signature hasn't
+ * been checked in case a subsequent call might have better luck.
+ */
+ 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.
+ */
+ if (!ocsp_CertIsOCSPDefaultResponder(handle, signerCert)) {
+ SECCertUsage certUsage;
+ if (CERT_IsCACert(signerCert, NULL)) {
+ certUsage = certUsageAnyCA;
+ } else {
+ certUsage = certUsageStatusResponder;
+ }
+ rv = cert_VerifyCertWithFlags(handle, signerCert, PR_TRUE, certUsage,
+ producedAt, CERT_VERIFYCERT_SKIP_OCSP,
+ pwArg, NULL);
+ if (rv != SECSuccess) {
+ PORT_SetError(SEC_ERROR_OCSP_INVALID_SIGNING_CERT);
+ goto finish;
+ }
+ }
+
+ rv = ocsp_VerifyResponseSignature(signerCert, signature,
+ tbsResponseDataDER,
+ pwArg);
+
+finish:
+ if (signature->wasChecked)
+ signature->status = rv;
+
+ if (rv != SECSuccess) {
+ signature->failureReason = PORT_GetError();
+ if (signerCert != NULL)
+ CERT_DestroyCertificate(signerCert);
+ } else {
+ /*
+ * Save signer's certificate in signature.
+ */
+ signature->cert = signerCert;
+ if (pSignerCert != NULL) {
+ /*
+ * Pass pointer to signer's certificate back to our caller,
+ * who is also now responsible for destroying it.
+ */
+ *pSignerCert = CERT_DupCertificate(signerCert);
+ }
+ }
+
+ return rv;
+}
+
+/*
+ * 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(CERTOCSPCertID *requestCertID,
+ CERTOCSPCertID *responseCertID)
+{
+ PRBool match = PR_FALSE;
+ SECOidTag hashAlg;
+ SECItem *keyHash = NULL;
+ SECItem *nameHash = NULL;
+
+ /*
+ * In order to match, they must have the same issuer and the same
+ * serial number.
+ *
+ * We just compare the easier things first.
+ */
+ if (SECITEM_CompareItem(&requestCertID->serialNumber,
+ &responseCertID->serialNumber) != SECEqual) {
+ goto done;
+ }
+
+ /*
+ * 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(&requestCertID->issuerNameHash,
+ &responseCertID->issuerNameHash) == SECEqual) &&
+ (SECITEM_CompareItem(&requestCertID->issuerKeyHash,
+ &responseCertID->issuerKeyHash) == SECEqual)) {
+ match = PR_TRUE;
+ }
+ goto done;
+ }
+
+ hashAlg = SECOID_FindOIDTag(&responseCertID->hashAlgorithm.algorithm);
+ switch (hashAlg) {
+ case SEC_OID_SHA1:
+ keyHash = &requestCertID->issuerSHA1KeyHash;
+ nameHash = &requestCertID->issuerSHA1NameHash;
+ break;
+ case SEC_OID_MD5:
+ keyHash = &requestCertID->issuerMD5KeyHash;
+ nameHash = &requestCertID->issuerMD5NameHash;
+ break;
+ case SEC_OID_MD2:
+ keyHash = &requestCertID->issuerMD2KeyHash;
+ nameHash = &requestCertID->issuerMD2NameHash;
+ break;
+ default:
+ PORT_SetError(SEC_ERROR_INVALID_ALGORITHM);
+ return PR_FALSE;
+ }
+
+ if ((keyHash != NULL) &&
+ (SECITEM_CompareItem(nameHash,
+ &responseCertID->issuerNameHash) == SECEqual) &&
+ (SECITEM_CompareItem(keyHash,
+ &responseCertID->issuerKeyHash) == SECEqual)) {
+ match = PR_TRUE;
+ }
+
+done:
+ return match;
+}
+
+/*
+ * Find the single response for the cert specified by certID.
+ * No copying is done; this just returns a pointer to the appropriate
+ * response within responses, if it is found (and null otherwise).
+ * This is fine, of course, since this function is internal-use only.
+ */
+static CERTOCSPSingleResponse *
+ocsp_GetSingleResponseForCertID(CERTOCSPSingleResponse **responses,
+ CERTCertDBHandle *handle,
+ CERTOCSPCertID *certID)
+{
+ CERTOCSPSingleResponse *single;
+ int i;
+
+ if (responses == NULL)
+ return NULL;
+
+ for (i = 0; responses[i] != NULL; i++) {
+ single = responses[i];
+ if (ocsp_CertIDsMatch(certID, single->certID)) {
+ return single;
+ }
+ }
+
+ /*
+ * The OCSP server should have included a response even if it knew
+ * nothing about the certificate in question. Since it did not,
+ * this will make it look as if it had.
+ *
+ * XXX Should we make this a separate error to notice the server's
+ * bad behavior?
+ */
+ PORT_SetError(SEC_ERROR_OCSP_UNKNOWN_CERT);
+ return NULL;
+}
+
+static ocspCheckingContext *
+ocsp_GetCheckingContext(CERTCertDBHandle *handle)
+{
+ CERTStatusConfig *statusConfig;
+ ocspCheckingContext *ocspcx = NULL;
+
+ statusConfig = CERT_GetStatusConfig(handle);
+ if (statusConfig != NULL) {
+ ocspcx = statusConfig->statusContext;
+
+ /*
+ * This is actually an internal error, because we should never
+ * have a good statusConfig without a good statusContext, too.
+ * For lack of anything better, though, we just assert and use
+ * the same error as if there were no statusConfig (set below).
+ */
+ PORT_Assert(ocspcx != NULL);
+ }
+
+ if (ocspcx == NULL)
+ PORT_SetError(SEC_ERROR_OCSP_NOT_ENABLED);
+
+ return ocspcx;
+}
+
+/*
+ * 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)
+{
+ ocspCheckingContext *ocspcx;
+
+ ocspcx = ocsp_GetCheckingContext(handle);
+ if (ocspcx == NULL)
+ goto loser;
+
+ /*
+ * 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) {
+ PORT_Assert(ocspcx->defaultResponderCert != NULL);
+ return ocspcx->defaultResponderCert;
+ }
+
+loser:
+ return NULL;
+}
+
+/*
+ * Return true if the cert is one of the default responders configured for
+ * ocsp context. If not, or if any error, return false.
+ */
+PRBool
+ocsp_CertIsOCSPDefaultResponder(CERTCertDBHandle *handle, CERTCertificate *cert)
+{
+ ocspCheckingContext *ocspcx;
+
+ 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;
+}
+
+/*
+ * Check that the given signer certificate is authorized to sign status
+ * information for the given certID. Return true if it is, false if not
+ * (or if there is any error along the way). If false is returned because
+ * the signer is not authorized, the following error will be set:
+ * SEC_ERROR_OCSP_UNAUTHORIZED_RESPONSE
+ * Other errors are low-level problems (no memory, bad database, etc.).
+ *
+ * There are three ways to be authorized. In the order in which we check,
+ * using the terms used in the OCSP spec, the signer must be one of:
+ * 1. A "trusted responder" -- it matches a local configuration
+ * of OCSP signing authority for the certificate in question.
+ * 2. The CA who issued the certificate in question.
+ * 3. A "CA designated responder", aka an "authorized responder" -- it
+ * must be represented by a special cert issued by the CA who issued
+ * the certificate in question.
+ */
+static PRBool
+ocsp_AuthorizedResponderForCertID(CERTCertDBHandle *handle,
+ CERTCertificate *signerCert,
+ CERTOCSPCertID *certID,
+ PRTime thisUpdate)
+{
+ CERTCertificate *issuerCert = NULL, *defRespCert;
+ SECItem *keyHash = NULL;
+ SECItem *nameHash = NULL;
+ SECOidTag hashAlg;
+ PRBool keyHashEQ = PR_FALSE, nameHashEQ = PR_FALSE;
+
+ /*
+ * Check first for a trusted responder, which overrides everything else.
+ */
+ 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.
+ */
+
+ hashAlg = SECOID_FindOIDTag(&certID->hashAlgorithm.algorithm);
+
+ keyHash = CERT_GetSubjectPublicKeyDigest(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;
+ }
+ }
+
+ keyHashEQ = PR_FALSE;
+ nameHashEQ = PR_FALSE;
+
+ if (!ocsp_CertIsOCSPDesignatedResponder(signerCert)) {
+ PORT_SetError(SEC_ERROR_OCSP_UNAUTHORIZED_RESPONSE);
+ return PR_FALSE;
+ }
+
+ /*
+ * 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;
+ }
+
+ keyHash = CERT_GetSubjectPublicKeyDigest(NULL, issuerCert, hashAlg, NULL);
+ nameHash = CERT_GetSubjectNameDigest(NULL, issuerCert, hashAlg, NULL);
+
+ CERT_DestroyCertificate(issuerCert);
+
+ if (keyHash != NULL && nameHash != NULL) {
+ keyHashEQ =
+ (SECITEM_CompareItem(keyHash,
+ &certID->issuerKeyHash) == SECEqual);
+
+ nameHashEQ =
+ (SECITEM_CompareItem(nameHash,
+ &certID->issuerNameHash) == SECEqual);
+ }
+
+ 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;
+}
+
+/*
+ * We need to check that a responder gives us "recent" information.
+ * Since a responder can pre-package responses, we need to pick an amount
+ * of time that is acceptable to us, and reject any response that is
+ * older than that.
+ *
+ * XXX This *should* be based on some configuration parameter, so that
+ * different usages could specify exactly what constitutes "sufficiently
+ * recent". But that is not going to happen right away. For now, we
+ * want something from within the last 24 hours. This macro defines that
+ * number in seconds.
+ */
+#define OCSP_ALLOWABLE_LAPSE_SECONDS (24L * 60L * 60L)
+
+static PRBool
+ocsp_TimeIsRecent(PRTime checkTime)
+{
+ PRTime now = PR_Now();
+ PRTime lapse, tmp;
+
+ LL_I2L(lapse, OCSP_ALLOWABLE_LAPSE_SECONDS);
+ LL_I2L(tmp, PR_USEC_PER_SEC);
+ LL_MUL(lapse, lapse, tmp); /* allowable lapse in microseconds */
+
+ LL_ADD(checkTime, checkTime, lapse);
+ if (LL_CMP(now, >, checkTime))
+ return PR_FALSE;
+
+ return PR_TRUE;
+}
+
+#define OCSP_SLOP (5L * 60L) /* OCSP responses are allowed to be 5 minutes \
+ in the future by default */
+
+static PRUint32 ocspsloptime = OCSP_SLOP; /* seconds */
+
+/*
+ * If an old response contains the revoked certificate status, we want
+ * to return SECSuccess so the response will be used.
+ */
+static SECStatus
+ocsp_HandleOldSingleResponse(CERTOCSPSingleResponse *single, PRTime time)
+{
+ SECStatus rv;
+ ocspCertStatus *status = single->certStatus;
+ if (status->certStatusType == ocspCertStatus_revoked) {
+ rv = ocsp_CertRevokedAfter(status->certStatusInfo.revokedInfo, time);
+ if (rv != SECSuccess &&
+ PORT_GetError() == SEC_ERROR_REVOKED_CERTIFICATE) {
+ /*
+ * Return SECSuccess now. The subsequent ocsp_CertRevokedAfter
+ * call in ocsp_CertHasGoodStatus will cause
+ * ocsp_CertHasGoodStatus to fail with
+ * SEC_ERROR_REVOKED_CERTIFICATE.
+ */
+ return SECSuccess;
+ }
+ }
+ PORT_SetError(SEC_ERROR_OCSP_OLD_RESPONSE);
+ return SECFailure;
+}
+
+/*
+ * Check that this single response is okay. A return of SECSuccess means:
+ * 1. The signer (represented by "signerCert") is authorized to give status
+ * for the cert represented by the individual response in "single".
+ * 2. The value of thisUpdate is earlier than now.
+ * 3. The value of producedAt is later than or the same as thisUpdate.
+ * 4. If nextUpdate is given:
+ * - The value of nextUpdate is later than now.
+ * - The value of producedAt is earlier than nextUpdate.
+ * Else if no nextUpdate:
+ * - The value of thisUpdate is fairly recent.
+ * - The value of producedAt is fairly recent.
+ * However we do not need to perform an explicit check for this last
+ * constraint because it is already guaranteed by checking that
+ * producedAt is later than thisUpdate and thisUpdate is recent.
+ * Oh, and any responder is "authorized" to say that a cert is unknown to it.
+ *
+ * If any of those checks fail, SECFailure is returned and an error is set:
+ * SEC_ERROR_OCSP_FUTURE_RESPONSE
+ * SEC_ERROR_OCSP_OLD_RESPONSE
+ * SEC_ERROR_OCSP_UNAUTHORIZED_RESPONSE
+ * Other errors are low-level problems (no memory, bad database, etc.).
+ */
+static SECStatus
+ocsp_VerifySingleResponse(CERTOCSPSingleResponse *single,
+ CERTCertDBHandle *handle,
+ CERTCertificate *signerCert,
+ PRTime producedAt)
+{
+ CERTOCSPCertID *certID = single->certID;
+ PRTime 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,
+ * but all this function is concerned with is validity of the response,
+ * not the status of the cert.
+ */
+ PORT_Assert(single->certStatus != NULL);
+ if (single->certStatus->certStatusType == ocspCertStatus_unknown)
+ return SECSuccess;
+
+ /*
+ * We need to extract "thisUpdate" for use below and to pass along
+ * to AuthorizedResponderForCertID in case it needs it for doing an
+ * issuer look-up.
+ */
+ rv = DER_GeneralizedTimeToTime(&thisUpdate, &single->thisUpdate);
+ if (rv != SECSuccess)
+ return rv;
+
+ /*
+ * First confirm that signerCert is authorized to give this status.
+ */
+ if (ocsp_AuthorizedResponderForCertID(handle, signerCert, certID,
+ thisUpdate) != PR_TRUE)
+ return SECFailure;
+
+ /*
+ * Now check the time stuff, as described above.
+ */
+ now = PR_Now();
+ /* allow slop time for future response */
+ LL_UI2L(tmstamp, ocspsloptime); /* get slop time in seconds */
+ LL_UI2L(tmp, PR_USEC_PER_SEC);
+ LL_MUL(tmp, tmstamp, tmp); /* convert the slop time to PRTime */
+ LL_ADD(tmstamp, tmp, now); /* add current time to it */
+
+ if (LL_CMP(thisUpdate, >, tmstamp) || LL_CMP(producedAt, <, thisUpdate)) {
+ PORT_SetError(SEC_ERROR_OCSP_FUTURE_RESPONSE);
+ return SECFailure;
+ }
+ if (single->nextUpdate != NULL) {
+ rv = DER_GeneralizedTimeToTime(&nextUpdate, single->nextUpdate);
+ if (rv != SECSuccess)
+ return rv;
+
+ LL_ADD(tmp, tmp, nextUpdate);
+ if (LL_CMP(tmp, <, now) || LL_CMP(producedAt, >, nextUpdate))
+ return ocsp_HandleOldSingleResponse(single, now);
+ } else if (ocsp_TimeIsRecent(thisUpdate) != PR_TRUE) {
+ return ocsp_HandleOldSingleResponse(single, now);
+ }
+
+ return SECSuccess;
+}
+
+/*
+ * FUNCTION: CERT_GetOCSPAuthorityInfoAccessLocation
+ * Get the value of the URI of the OCSP responder for the given cert.
+ * This is found in the (optional) Authority Information Access extension
+ * in the cert.
+ * INPUTS:
+ * CERTCertificate *cert
+ * The certificate being examined.
+ * RETURN:
+ * 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_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.
+ */
+char *
+CERT_GetOCSPAuthorityInfoAccessLocation(const CERTCertificate *cert)
+{
+ CERTGeneralName *locname = NULL;
+ SECItem *location = NULL;
+ SECItem *encodedAuthInfoAccess = NULL;
+ CERTAuthInfoAccess **authInfoAccess = NULL;
+ char *locURI = NULL;
+ PLArenaPool *arena = NULL;
+ SECStatus rv;
+ int i;
+
+ /*
+ * Allocate this one from the heap because it will get filled in
+ * by CERT_FindCertExtension which will also allocate from the heap,
+ * and we can free the entire thing on our way out.
+ */
+ encodedAuthInfoAccess = SECITEM_AllocItem(NULL, NULL, 0);
+ if (encodedAuthInfoAccess == NULL)
+ goto loser;
+
+ rv = CERT_FindCertExtension(cert, SEC_OID_X509_AUTH_INFO_ACCESS,
+ encodedAuthInfoAccess);
+ 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
+ * this arena, which is temporary just for us to decode and get at the
+ * AIA extension. The whole thing will be destroyed on our way out,
+ * after we have copied the location string (url) itself (if found).
+ */
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (arena == NULL)
+ goto loser;
+
+ authInfoAccess = CERT_DecodeAuthInfoAccessExtension(arena,
+ encodedAuthInfoAccess);
+ if (authInfoAccess == NULL)
+ goto loser;
+
+ for (i = 0; authInfoAccess[i] != NULL; i++) {
+ if (SECOID_FindOIDTag(&authInfoAccess[i]->method) == SEC_OID_PKIX_OCSP)
+ locname = authInfoAccess[i]->location;
+ }
+
+ /*
+ * If we found an AIA extension, but it did not include an OCSP method,
+ * that should look to our caller as if we did not find the extension
+ * at all, because it is only an OCSP method that we care about.
+ * So set the same error that would be set if the AIA extension was
+ * not there at all.
+ */
+ if (locname == NULL) {
+ PORT_SetError(SEC_ERROR_CERT_BAD_ACCESS_LOCATION);
+ goto loser;
+ }
+
+ /*
+ * The following is just a pointer back into locname (i.e. not a copy);
+ * thus it should not be freed.
+ */
+ location = CERT_GetGeneralNameByType(locname, certURI, PR_FALSE);
+ if (location == NULL) {
+ /*
+ * XXX Appears that CERT_GetGeneralNameByType does not set an
+ * error if there is no name by that type. For lack of anything
+ * better, act as if the extension was not found. In the future
+ * this should probably be something more like the extension was
+ * badly formed.
+ */
+ PORT_SetError(SEC_ERROR_CERT_BAD_ACCESS_LOCATION);
+ goto loser;
+ }
+
+ /*
+ * That location is really a string, but it has a specified length
+ * without a null-terminator. We need a real string that does have
+ * a null-terminator, and we need a copy of it anyway to return to
+ * our caller -- so allocate and copy.
+ */
+ locURI = PORT_Alloc(location->len + 1);
+ if (locURI == NULL) {
+ goto loser;
+ }
+ PORT_Memcpy(locURI, location->data, location->len);
+ locURI[location->len] = '\0';
+
+loser:
+ if (arena != NULL)
+ PORT_FreeArena(arena, PR_FALSE);
+
+ if (encodedAuthInfoAccess != NULL)
+ SECITEM_FreeItem(encodedAuthInfoAccess, PR_TRUE);
+
+ return locURI;
+}
+
+/*
+ * Figure out where we should go to find out the status of the given cert
+ * via OCSP. If allowed to use a default responder uri and a default
+ * responder is set up, then that is our answer.
+ * If not, see if the certificate has an Authority Information Access (AIA)
+ * extension for OCSP, and return the value of that. Otherwise return NULL.
+ * We also let our caller know whether or not the responder chosen was
+ * a default responder or not through the output variable isDefault;
+ * its value has no meaning unless a good (non-null) value is returned
+ * for the location.
+ *
+ * The result needs to be freed (PORT_Free) when no longer in use.
+ */
+char *
+ocsp_GetResponderLocation(CERTCertDBHandle *handle, CERTCertificate *cert,
+ PRBool canUseDefault, PRBool *isDefault)
+{
+ ocspCheckingContext *ocspcx = NULL;
+ char *ocspUrl = NULL;
+
+ if (canUseDefault) {
+ ocspcx = ocsp_GetCheckingContext(handle);
+ }
+ if (ocspcx != NULL && ocspcx->useDefaultResponder) {
+ /*
+ * A default responder wins out, if specified.
+ * XXX Someday this may be a more complicated determination based
+ * on the cert's issuer. (That is, we could have different default
+ * responders configured for different issuers.)
+ */
+ PORT_Assert(ocspcx->defaultResponderURI != NULL);
+ *isDefault = PR_TRUE;
+ return (PORT_Strdup(ocspcx->defaultResponderURI));
+ }
+
+ /*
+ * No default responder set up, so go see if we can find an AIA
+ * extension that has a value for OCSP, and get the url from that.
+ */
+ *isDefault = PR_FALSE;
+ ocspUrl = CERT_GetOCSPAuthorityInfoAccessLocation(cert);
+ if (!ocspUrl) {
+ CERT_StringFromCertFcn altFcn;
+
+ PR_EnterMonitor(OCSP_Global.monitor);
+ altFcn = OCSP_Global.alternateOCSPAIAFcn;
+ PR_ExitMonitor(OCSP_Global.monitor);
+ if (altFcn) {
+ ocspUrl = (*altFcn)(cert);
+ if (ocspUrl)
+ *isDefault = PR_TRUE;
+ }
+ }
+ return ocspUrl;
+}
+
+/*
+ * Return SECSuccess if the cert was revoked *after* "time",
+ * SECFailure otherwise.
+ */
+static SECStatus
+ocsp_CertRevokedAfter(ocspRevokedInfo *revokedInfo, PRTime time)
+{
+ PRTime revokedTime;
+ SECStatus rv;
+
+ rv = DER_GeneralizedTimeToTime(&revokedTime, &revokedInfo->revocationTime);
+ if (rv != SECSuccess)
+ return rv;
+
+ /*
+ * Set the error even if we will return success; someone might care.
+ */
+ PORT_SetError(SEC_ERROR_REVOKED_CERTIFICATE);
+
+ if (LL_CMP(revokedTime, >, time))
+ return SECSuccess;
+
+ return SECFailure;
+}
+
+/*
+ * See if the cert represented in the single response had a good status
+ * at the specified time.
+ */
+SECStatus
+ocsp_CertHasGoodStatus(ocspCertStatus *status, PRTime time)
+{
+ SECStatus rv;
+ 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;
+ }
+ return rv;
+}
+
+static SECStatus
+ocsp_SingleResponseCertHasGoodStatus(CERTOCSPSingleResponse *single,
+ PRTime time)
+{
+ return ocsp_CertHasGoodStatus(single->certStatus, time);
+}
+
+/* SECFailure means the arguments were invalid.
+ * On SECSuccess, the out parameters contain the OCSP status.
+ * rvOcsp contains the overall result of the OCSP operation.
+ * Depending on input parameter ignoreGlobalOcspFailureSetting,
+ * a soft failure might be converted into *rvOcsp=SECSuccess.
+ * If the cached attempt to obtain OCSP information had resulted
+ * in a failure, missingResponseError shows the error code of
+ * that failure.
+ * cacheFreshness is ocspMissing if no entry was found,
+ * ocspFresh if a fresh entry was found, or
+ * ocspStale if a stale entry was found.
+ */
+SECStatus
+ocsp_GetCachedOCSPResponseStatus(CERTOCSPCertID *certID,
+ PRTime time,
+ PRBool ignoreGlobalOcspFailureSetting,
+ SECStatus *rvOcsp,
+ SECErrorCodes *missingResponseError,
+ OCSPFreshness *cacheFreshness)
+{
+ OCSPCacheItem *cacheItem = NULL;
+
+ if (!certID || !missingResponseError || !rvOcsp || !cacheFreshness) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+ *rvOcsp = SECFailure;
+ *missingResponseError = 0;
+ *cacheFreshness = ocspMissing;
+
+ PR_EnterMonitor(OCSP_Global.monitor);
+ cacheItem = ocsp_FindCacheEntry(&OCSP_Global.cache, certID);
+ if (cacheItem) {
+ *cacheFreshness = ocsp_IsCacheItemFresh(cacheItem) ? ocspFresh
+ : ocspStale;
+ /* having an arena means, we have a cached certStatus */
+ if (cacheItem->certStatusArena) {
+ *rvOcsp = ocsp_CertHasGoodStatus(&cacheItem->certStatus, time);
+ if (*rvOcsp != SECSuccess) {
+ *missingResponseError = PORT_GetError();
+ }
+ } 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 (*cacheFreshness == ocspFresh &&
+ !ignoreGlobalOcspFailureSetting &&
+ OCSP_Global.ocspFailureMode ==
+ ocspMode_FailureIsNotAVerificationFailure) {
+ *rvOcsp = SECSuccess;
+ }
+ *missingResponseError = cacheItem->missingResponseError;
+ }
+ }
+ PR_ExitMonitor(OCSP_Global.monitor);
+ return SECSuccess;
+}
+
+PRBool
+ocsp_FetchingFailureIsVerificationFailure(void)
+{
+ PRBool isFailure;
+
+ PR_EnterMonitor(OCSP_Global.monitor);
+ isFailure =
+ OCSP_Global.ocspFailureMode == ocspMode_FailureIsVerificationFailure;
+ PR_ExitMonitor(OCSP_Global.monitor);
+ return isFailure;
+}
+
+/*
+ * FUNCTION: CERT_CheckOCSPStatus
+ * Checks the status of a certificate via OCSP. Will only check status for
+ * a certificate that has an AIA (Authority Information Access) extension
+ * for OCSP *or* when a "default responder" is specified and enabled.
+ * (If no AIA extension for OCSP and no default responder in place, the
+ * cert is considered to have a good status and SECSuccess is returned.)
+ * INPUTS:
+ * CERTCertDBHandle *handle
+ * certificate DB of the cert that is being checked
+ * CERTCertificate *cert
+ * the certificate being checked
+ * XXX in the long term also need a boolean parameter that specifies
+ * whether to check the cert chain, as well; for now we check only
+ * the leaf (the specified certificate)
+ * PRTime time
+ * time for which status is to be determined
+ * void *pwArg
+ * argument for password prompting, if needed
+ * RETURN:
+ * Returns SECSuccess if an approved OCSP responder "knows" the cert
+ * *and* returns a non-revoked status for it; SECFailure otherwise,
+ * with an error set describing the reason:
+ *
+ * SEC_ERROR_OCSP_BAD_HTTP_RESPONSE
+ * SEC_ERROR_OCSP_FUTURE_RESPONSE
+ * SEC_ERROR_OCSP_MALFORMED_REQUEST
+ * SEC_ERROR_OCSP_MALFORMED_RESPONSE
+ * SEC_ERROR_OCSP_OLD_RESPONSE
+ * SEC_ERROR_OCSP_REQUEST_NEEDS_SIG
+ * SEC_ERROR_OCSP_SERVER_ERROR
+ * SEC_ERROR_OCSP_TRY_SERVER_LATER
+ * SEC_ERROR_OCSP_UNAUTHORIZED_REQUEST
+ * SEC_ERROR_OCSP_UNAUTHORIZED_RESPONSE
+ * SEC_ERROR_OCSP_UNKNOWN_CERT
+ * SEC_ERROR_OCSP_UNKNOWN_RESPONSE_STATUS
+ * SEC_ERROR_OCSP_UNKNOWN_RESPONSE_TYPE
+ *
+ * SEC_ERROR_BAD_SIGNATURE
+ * SEC_ERROR_CERT_BAD_ACCESS_LOCATION
+ * SEC_ERROR_INVALID_TIME
+ * SEC_ERROR_REVOKED_CERTIFICATE
+ * SEC_ERROR_UNKNOWN_ISSUER
+ * SEC_ERROR_UNKNOWN_SIGNER
+ *
+ * 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 (error allocating
+ * memory, error performing ASN.1 decoding, etc.).
+ */
+SECStatus
+CERT_CheckOCSPStatus(CERTCertDBHandle *handle, CERTCertificate *cert,
+ PRTime time, void *pwArg)
+{
+ CERTOCSPCertID *certID;
+ PRBool certIDWasConsumed = PR_FALSE;
+ SECStatus rv;
+ SECStatus rvOcsp;
+ SECErrorCodes cachedErrorCode;
+ OCSPFreshness cachedResponseFreshness;
+
+ OCSP_TRACE_CERT(cert);
+ OCSP_TRACE_TIME("## requested validity time:", time);
+
+ certID = CERT_CreateOCSPCertID(cert, time);
+ if (!certID)
+ return SECFailure;
+ rv = ocsp_GetCachedOCSPResponseStatus(
+ certID, time, PR_FALSE, /* ignoreGlobalOcspFailureSetting */
+ &rvOcsp, &cachedErrorCode, &cachedResponseFreshness);
+ if (rv != SECSuccess) {
+ CERT_DestroyOCSPCertID(certID);
+ return SECFailure;
+ }
+ if (cachedResponseFreshness == ocspFresh) {
+ CERT_DestroyOCSPCertID(certID);
+ if (rvOcsp != SECSuccess) {
+ PORT_SetError(cachedErrorCode);
+ }
+ return rvOcsp;
+ }
+
+ rv = ocsp_GetOCSPStatusFromNetwork(handle, certID, cert, time, pwArg,
+ &certIDWasConsumed,
+ &rvOcsp);
+ if (rv != SECSuccess) {
+ PRErrorCode err = PORT_GetError();
+ if (ocsp_FetchingFailureIsVerificationFailure()) {
+ PORT_SetError(err);
+ rvOcsp = SECFailure;
+ } else if (cachedResponseFreshness == ocspStale &&
+ (cachedErrorCode == SEC_ERROR_OCSP_UNKNOWN_CERT ||
+ cachedErrorCode == SEC_ERROR_REVOKED_CERTIFICATE)) {
+ /* If we couldn't get a response for a certificate that the OCSP
+ * responder previously told us was bad, then assume it is still
+ * bad until we hear otherwise, as it is very unlikely that the
+ * certificate status has changed from "revoked" to "good" and it
+ * is also unlikely that the certificate status has changed from
+ * "unknown" to "good", except for some buggy OCSP responders.
+ */
+ PORT_SetError(cachedErrorCode);
+ rvOcsp = SECFailure;
+ } else {
+ rvOcsp = SECSuccess;
+ }
+ }
+ if (!certIDWasConsumed) {
+ CERT_DestroyOCSPCertID(certID);
+ }
+ return rvOcsp;
+}
+
+/*
+ * FUNCTION: CERT_CacheOCSPResponseFromSideChannel
+ * First, this function checks the OCSP cache to see if a good response
+ * for the given certificate already exists. If it does, then the function
+ * returns successfully.
+ *
+ * If not, then it validates that the given OCSP response is a valid,
+ * good response for the given certificate and inserts it into the
+ * cache.
+ *
+ * This function is intended for use when OCSP responses are provided via a
+ * side-channel, i.e. TLS OCSP stapling (a.k.a. the status_request extension).
+ *
+ * INPUTS:
+ * CERTCertDBHandle *handle
+ * certificate DB of the cert that is being checked
+ * CERTCertificate *cert
+ * the certificate being checked
+ * PRTime time
+ * time for which status is to be determined
+ * SECItem *encodedResponse
+ * the DER encoded bytes of the OCSP response
+ * void *pwArg
+ * argument for password prompting, if needed
+ * RETURN:
+ * SECSuccess if the cert was found in the cache, or if the OCSP response was
+ * found to be valid and inserted into the cache. SECFailure otherwise.
+ */
+SECStatus
+CERT_CacheOCSPResponseFromSideChannel(CERTCertDBHandle *handle,
+ CERTCertificate *cert,
+ PRTime time,
+ const SECItem *encodedResponse,
+ void *pwArg)
+{
+ CERTOCSPCertID *certID = NULL;
+ PRBool certIDWasConsumed = PR_FALSE;
+ SECStatus rv = SECFailure;
+ SECStatus rvOcsp = SECFailure;
+ SECErrorCodes dummy_error_code; /* we ignore this */
+ CERTOCSPResponse *decodedResponse = NULL;
+ CERTOCSPSingleResponse *singleResponse = NULL;
+ OCSPFreshness freshness;
+
+ /* The OCSP cache can be in three states regarding this certificate:
+ * + Good (cached, timely, 'good' response, or revoked in the future)
+ * + Revoked (cached, timely, but doesn't fit in the last category)
+ * + Miss (no knowledge)
+ *
+ * Likewise, the side-channel information can be
+ * + Good (timely, 'good' response, or revoked in the future)
+ * + Revoked (timely, but doesn't fit in the last category)
+ * + Invalid (bad syntax, bad signature, not timely etc)
+ *
+ * The common case is that the cache result is Good and so is the
+ * side-channel information. We want to save processing time in this case
+ * so we say that any time we see a Good result from the cache we return
+ * early.
+ *
+ * Cache result
+ * | Good Revoked Miss
+ * ---+--------------------------------------------
+ * G | noop Cache more Cache it
+ * S | recent result
+ * i |
+ * d |
+ * e |
+ * R | noop Cache more Cache it
+ * C | recent result
+ * h |
+ * a |
+ * n |
+ * n I | noop Noop Noop
+ * e |
+ * l |
+ *
+ * When we fetch from the network we might choose to cache a negative
+ * result when the response is invalid. This saves us hammering, uselessly,
+ * at a broken responder. However, side channels are commonly attacker
+ * controlled and so we must not cache a negative result for an Invalid
+ * side channel.
+ */
+
+ if (!cert || !encodedResponse) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+ certID = CERT_CreateOCSPCertID(cert, time);
+ if (!certID)
+ return SECFailure;
+
+ /* We pass PR_TRUE for ignoreGlobalOcspFailureSetting so that a cached
+ * error entry is not interpreted as being a 'Good' entry here.
+ */
+ rv = ocsp_GetCachedOCSPResponseStatus(
+ certID, time, PR_TRUE, /* ignoreGlobalOcspFailureSetting */
+ &rvOcsp, &dummy_error_code, &freshness);
+ if (rv == SECSuccess && rvOcsp == SECSuccess && freshness == ocspFresh) {
+ /* The cached value is good. We don't want to waste time validating
+ * this OCSP response. This is the first column in the table above. */
+ CERT_DestroyOCSPCertID(certID);
+ return rv;
+ }
+
+ /* The logic for caching the more recent response is handled in
+ * ocsp_CacheSingleResponse. */
+
+ rv = ocsp_GetDecodedVerifiedSingleResponseForID(handle, certID, cert,
+ time, pwArg,
+ encodedResponse,
+ &decodedResponse,
+ &singleResponse);
+ if (rv == SECSuccess) {
+ rvOcsp = ocsp_SingleResponseCertHasGoodStatus(singleResponse, time);
+ /* Cache any valid singleResponse, regardless of status. */
+ ocsp_CacheSingleResponse(certID, singleResponse, &certIDWasConsumed);
+ }
+ if (decodedResponse) {
+ CERT_DestroyOCSPResponse(decodedResponse);
+ }
+ if (!certIDWasConsumed) {
+ CERT_DestroyOCSPCertID(certID);
+ }
+ return rv == SECSuccess ? rvOcsp : rv;
+}
+
+/*
+ * Status in *certIDWasConsumed will always be correct, regardless of
+ * return value.
+ */
+static SECStatus
+ocsp_GetOCSPStatusFromNetwork(CERTCertDBHandle *handle,
+ CERTOCSPCertID *certID,
+ CERTCertificate *cert,
+ PRTime time,
+ void *pwArg,
+ PRBool *certIDWasConsumed,
+ SECStatus *rv_ocsp)
+{
+ char *location = NULL;
+ PRBool locationIsDefault;
+ SECItem *encodedResponse = NULL;
+ CERTOCSPRequest *request = NULL;
+ SECStatus rv = SECFailure;
+
+ CERTOCSPResponse *decodedResponse = NULL;
+ CERTOCSPSingleResponse *singleResponse = NULL;
+ enum { stageGET,
+ stagePOST } currentStage;
+ PRBool retry = PR_FALSE;
+
+ if (!certIDWasConsumed || !rv_ocsp) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+ *certIDWasConsumed = PR_FALSE;
+ *rv_ocsp = SECFailure;
+
+ if (!OCSP_Global.monitor) {
+ PORT_SetError(SEC_ERROR_NOT_INITIALIZED);
+ return SECFailure;
+ }
+ PR_EnterMonitor(OCSP_Global.monitor);
+ if (OCSP_Global.forcePost) {
+ currentStage = stagePOST;
+ } else {
+ currentStage = stageGET;
+ }
+ PR_ExitMonitor(OCSP_Global.monitor);
+
+ /*
+ * The first thing we need to do is find the location of the responder.
+ * This will be the value of the default responder (if enabled), else
+ * it will come out of the AIA extension in the cert (if present).
+ * If we have no such location, then this cert does not "deserve" to
+ * be checked -- that is, we consider it a success and just return.
+ * The way we tell that is by looking at the error number to see if
+ * the problem was no AIA extension was found; any other error was
+ * a true failure that we unfortunately have to treat as an overall
+ * failure here.
+ */
+ location = ocsp_GetResponderLocation(handle, cert, PR_TRUE,
+ &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);
+ *rv_ocsp = SECSuccess;
+ return SECSuccess;
+ }
+ return SECFailure;
+ }
+
+ /*
+ * 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
+ * case, handling it is complicated because we may need to send as
+ * many requests (and receive as many responses) as we have certs
+ * in the chain. If we are going to talk to a default responder,
+ * and we only support one default responder, we can put all of the
+ * certs together into one request. Otherwise, we must break them up
+ * into multiple requests. (Even if all of the requests will go to
+ * the same location, the signature on each response will be different,
+ * because each issuer is different. Carefully read the OCSP spec
+ * if you do not understand this.)
+ */
+
+ /*
+ * 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.
+ */
+
+ do {
+ const char *method;
+ PRBool validResponseWithAccurateInfo = PR_FALSE;
+ retry = PR_FALSE;
+ *rv_ocsp = SECFailure;
+
+ if (currentStage == stageGET) {
+ method = "GET";
+ } else {
+ PORT_Assert(currentStage == stagePOST);
+ method = "POST";
+ }
+
+ encodedResponse =
+ ocsp_GetEncodedOCSPResponseForSingleCert(NULL, certID, cert,
+ location, method,
+ time, locationIsDefault,
+ pwArg, &request);
+
+ if (encodedResponse) {
+ rv = ocsp_GetDecodedVerifiedSingleResponseForID(handle, certID, cert,
+ time, pwArg,
+ encodedResponse,
+ &decodedResponse,
+ &singleResponse);
+ if (rv == SECSuccess) {
+ switch (singleResponse->certStatus->certStatusType) {
+ case ocspCertStatus_good:
+ case ocspCertStatus_revoked:
+ validResponseWithAccurateInfo = PR_TRUE;
+ break;
+ default:
+ break;
+ }
+ *rv_ocsp = ocsp_SingleResponseCertHasGoodStatus(singleResponse, time);
+ }
+ }
+
+ if (currentStage == stageGET) {
+ /* only accept GET response if good or revoked */
+ if (validResponseWithAccurateInfo) {
+ ocsp_CacheSingleResponse(certID, singleResponse,
+ certIDWasConsumed);
+ } else {
+ retry = PR_TRUE;
+ currentStage = stagePOST;
+ }
+ } else {
+ /* cache the POST respone, regardless of status */
+ if (!singleResponse) {
+ cert_RememberOCSPProcessingFailure(certID, certIDWasConsumed);
+ } else {
+ ocsp_CacheSingleResponse(certID, singleResponse,
+ certIDWasConsumed);
+ }
+ }
+
+ if (encodedResponse) {
+ SECITEM_FreeItem(encodedResponse, PR_TRUE);
+ encodedResponse = NULL;
+ }
+ if (request) {
+ CERT_DestroyOCSPRequest(request);
+ request = NULL;
+ }
+ if (decodedResponse) {
+ CERT_DestroyOCSPResponse(decodedResponse);
+ decodedResponse = NULL;
+ }
+ singleResponse = NULL;
+
+ } while (retry);
+
+ PORT_Free(location);
+ return rv;
+}
+
+/*
+ * FUNCTION: ocsp_GetDecodedVerifiedSingleResponseForID
+ * This function decodes an OCSP response and checks for a valid response
+ * concerning the given certificate.
+ *
+ * Note: a 'valid' response is one that parses successfully, is not an OCSP
+ * exception (see RFC 2560 Section 2.3), is correctly signed and is current.
+ * A 'good' response is a valid response that attests that the certificate
+ * is not currently revoked (see RFC 2560 Section 2.2).
+ *
+ * INPUTS:
+ * CERTCertDBHandle *handle
+ * certificate DB of the cert that is being checked
+ * CERTOCSPCertID *certID
+ * the cert ID corresponding to |cert|
+ * CERTCertificate *cert
+ * the certificate being checked
+ * PRTime time
+ * time for which status is to be determined
+ * void *pwArg
+ * the opaque argument to the password prompting function.
+ * SECItem *encodedResponse
+ * the DER encoded bytes of the OCSP response
+ * CERTOCSPResponse **pDecodedResponse
+ * (output) The caller must ALWAYS check for this output parameter,
+ * and if it's non-null, must destroy it using CERT_DestroyOCSPResponse.
+ * CERTOCSPSingleResponse **pSingle
+ * (output) on success, this points to the single response that corresponds
+ * to the certID parameter. Points to the inside of pDecodedResponse.
+ * It isn't a copy, don't free it.
+ * RETURN:
+ * SECSuccess iff the response is valid.
+ */
+static SECStatus
+ocsp_GetDecodedVerifiedSingleResponseForID(CERTCertDBHandle *handle,
+ CERTOCSPCertID *certID,
+ CERTCertificate *cert,
+ PRTime time,
+ void *pwArg,
+ const SECItem *encodedResponse,
+ CERTOCSPResponse **pDecodedResponse,
+ CERTOCSPSingleResponse **pSingle)
+{
+ CERTCertificate *signerCert = NULL;
+ CERTCertificate *issuerCert = NULL;
+ SECStatus rv = SECFailure;
+
+ if (!pSingle || !pDecodedResponse) {
+ return SECFailure;
+ }
+ *pSingle = NULL;
+ *pDecodedResponse = CERT_DecodeOCSPResponse(encodedResponse);
+ if (!*pDecodedResponse) {
+ return SECFailure;
+ }
+
+ /*
+ * Okay, we at least have a response that *looks* like a response!
+ * Now see if the overall response status value is good or not.
+ * If not, we set an error and give up. (It means that either the
+ * server had a problem, or it didn't like something about our
+ * request. Either way there is nothing to do but give up.)
+ * Otherwise, we continue to find the actual per-cert status
+ * in the response.
+ */
+ if (CERT_GetOCSPResponseStatus(*pDecodedResponse) != SECSuccess) {
+ goto loser;
+ }
+
+ /*
+ * If we've made it this far, we expect a response with a good signature.
+ * So, check for that.
+ */
+ issuerCert = CERT_FindCertIssuer(cert, time, certUsageAnyCA);
+ rv = CERT_VerifyOCSPResponseSignature(*pDecodedResponse, handle, pwArg,
+ &signerCert, issuerCert);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ PORT_Assert(signerCert != NULL); /* internal consistency check */
+ /* XXX probably should set error, return failure if signerCert is null */
+
+ /*
+ * 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.
+ */
+ rv = ocsp_GetVerifiedSingleResponseForCertID(handle, *pDecodedResponse, certID,
+ signerCert, time, pSingle);
+loser:
+ if (issuerCert != NULL)
+ CERT_DestroyCertificate(issuerCert);
+ if (signerCert != NULL)
+ CERT_DestroyCertificate(signerCert);
+ return rv;
+}
+
+/*
+ * FUNCTION: ocsp_CacheSingleResponse
+ * This function requires that the caller has checked that the response
+ * is valid and verified.
+ * The (positive or negative) valid response will be used to update the cache.
+ * INPUTS:
+ * CERTOCSPCertID *certID
+ * the cert ID corresponding to |cert|
+ * PRBool *certIDWasConsumed
+ * (output) on return, this is true iff |certID| was consumed by this
+ * function.
+ */
+void
+ocsp_CacheSingleResponse(CERTOCSPCertID *certID,
+ CERTOCSPSingleResponse *single,
+ PRBool *certIDWasConsumed)
+{
+ if (single != NULL) {
+ PR_EnterMonitor(OCSP_Global.monitor);
+ if (OCSP_Global.maxCacheEntries >= 0) {
+ ocsp_CreateOrUpdateCacheEntry(&OCSP_Global.cache, certID, single,
+ certIDWasConsumed);
+ /* ignore cache update failures */
+ }
+ PR_ExitMonitor(OCSP_Global.monitor);
+ }
+}
+
+SECStatus
+ocsp_GetVerifiedSingleResponseForCertID(CERTCertDBHandle *handle,
+ CERTOCSPResponse *response,
+ CERTOCSPCertID *certID,
+ CERTCertificate *signerCert,
+ PRTime time,
+ CERTOCSPSingleResponse
+ **pSingleResponse)
+{
+ SECStatus rv;
+ ocspResponseData *responseData;
+ PRTime producedAt;
+ CERTOCSPSingleResponse *single;
+
+ /*
+ * The ResponseData part is the real guts of the response.
+ */
+ responseData = ocsp_GetResponseData(response, NULL);
+ if (responseData == NULL) {
+ rv = SECFailure;
+ goto loser;
+ }
+
+ /*
+ * There is one producedAt time for the entire response (and a separate
+ * thisUpdate time for each individual single response). We need to
+ * compare them, so get the overall time to pass into the check of each
+ * single response.
+ */
+ rv = DER_GeneralizedTimeToTime(&producedAt, &responseData->producedAt);
+ if (rv != SECSuccess)
+ goto loser;
+
+ single = ocsp_GetSingleResponseForCertID(responseData->responses,
+ handle, certID);
+ if (single == NULL) {
+ rv = SECFailure;
+ goto loser;
+ }
+
+ rv = ocsp_VerifySingleResponse(single, handle, signerCert, producedAt);
+ if (rv != SECSuccess)
+ goto loser;
+ *pSingleResponse = single;
+
+loser:
+ return rv;
+}
+
+SECStatus
+CERT_GetOCSPStatusForCertID(CERTCertDBHandle *handle,
+ CERTOCSPResponse *response,
+ CERTOCSPCertID *certID,
+ CERTCertificate *signerCert,
+ PRTime time)
+{
+ /*
+ * We do not update the cache, because:
+ *
+ * CERT_GetOCSPStatusForCertID is an old exported API that was introduced
+ * before the OCSP cache got implemented.
+ *
+ * The implementation of helper function cert_ProcessOCSPResponse
+ * requires the ability to transfer ownership of the the given certID to
+ * the cache. The external API doesn't allow us to prevent the caller from
+ * destroying the certID. We don't have the original certificate available,
+ * therefore we are unable to produce another certID object (that could
+ * be stored in the cache).
+ *
+ * Should we ever implement code to produce a deep copy of certID,
+ * then this could be changed to allow updating the cache.
+ * The duplication would have to be done in
+ * cert_ProcessOCSPResponse, if the out parameter to indicate
+ * a transfer of ownership is NULL.
+ */
+ return cert_ProcessOCSPResponse(handle, response, certID,
+ signerCert, time,
+ NULL, NULL);
+}
+
+/*
+ * The first 5 parameters match the definition of CERT_GetOCSPStatusForCertID.
+ */
+SECStatus
+cert_ProcessOCSPResponse(CERTCertDBHandle *handle,
+ CERTOCSPResponse *response,
+ CERTOCSPCertID *certID,
+ CERTCertificate *signerCert,
+ PRTime time,
+ PRBool *certIDWasConsumed,
+ SECStatus *cacheUpdateStatus)
+{
+ SECStatus rv;
+ SECStatus rv_cache = SECSuccess;
+ CERTOCSPSingleResponse *single = NULL;
+
+ rv = ocsp_GetVerifiedSingleResponseForCertID(handle, response, certID,
+ signerCert, time, &single);
+ if (rv == SECSuccess) {
+ /*
+ * Check whether the status says revoked, and if so
+ * how that compares to the time value passed into this routine.
+ */
+ rv = ocsp_SingleResponseCertHasGoodStatus(single, time);
+ }
+
+ if (certIDWasConsumed) {
+ /*
+ * We don't have copy-of-certid implemented. In order to update
+ * the cache, the caller must supply an out variable
+ * certIDWasConsumed, allowing us to return ownership status.
+ */
+
+ PR_EnterMonitor(OCSP_Global.monitor);
+ if (OCSP_Global.maxCacheEntries >= 0) {
+ /* single == NULL means: remember response failure */
+ rv_cache =
+ ocsp_CreateOrUpdateCacheEntry(&OCSP_Global.cache, certID,
+ single, certIDWasConsumed);
+ }
+ PR_ExitMonitor(OCSP_Global.monitor);
+ if (cacheUpdateStatus) {
+ *cacheUpdateStatus = rv_cache;
+ }
+ }
+
+ return rv;
+}
+
+SECStatus
+cert_RememberOCSPProcessingFailure(CERTOCSPCertID *certID,
+ PRBool *certIDWasConsumed)
+{
+ SECStatus rv = SECSuccess;
+ PR_EnterMonitor(OCSP_Global.monitor);
+ if (OCSP_Global.maxCacheEntries >= 0) {
+ rv = ocsp_CreateOrUpdateCacheEntry(&OCSP_Global.cache, certID, NULL,
+ certIDWasConsumed);
+ }
+ PR_ExitMonitor(OCSP_Global.monitor);
+ return rv;
+}
+
+/*
+ * Disable status checking and destroy related structures/data.
+ */
+static SECStatus
+ocsp_DestroyStatusChecking(CERTStatusConfig *statusConfig)
+{
+ ocspCheckingContext *statusContext;
+
+ /*
+ * Disable OCSP checking
+ */
+ statusConfig->statusChecker = NULL;
+
+ statusContext = statusConfig->statusContext;
+ PORT_Assert(statusContext != NULL);
+ if (statusContext == NULL)
+ return SECFailure;
+
+ if (statusContext->defaultResponderURI != NULL)
+ PORT_Free(statusContext->defaultResponderURI);
+ if (statusContext->defaultResponderNickname != NULL)
+ PORT_Free(statusContext->defaultResponderNickname);
+
+ PORT_Free(statusContext);
+ statusConfig->statusContext = NULL;
+
+ PORT_Free(statusConfig);
+
+ return SECSuccess;
+}
+
+/*
+ * FUNCTION: CERT_DisableOCSPChecking
+ * Turns off OCSP checking for the given certificate database.
+ * This routine disables OCSP checking. Though it will return
+ * SECFailure if OCSP checking is not enabled, it is "safe" to
+ * call it that way and just ignore the return value, if it is
+ * easier to just call it than to "remember" whether it is enabled.
+ * INPUTS:
+ * CERTCertDBHandle *handle
+ * Certificate database for which OCSP checking will be disabled.
+ * RETURN:
+ * Returns SECFailure if an error occurred (usually means that OCSP
+ * checking was not enabled or status contexts were not initialized --
+ * error set will be SEC_ERROR_OCSP_NOT_ENABLED); SECSuccess otherwise.
+ */
+SECStatus
+CERT_DisableOCSPChecking(CERTCertDBHandle *handle)
+{
+ CERTStatusConfig *statusConfig;
+ ocspCheckingContext *statusContext;
+
+ if (handle == NULL) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+
+ statusConfig = CERT_GetStatusConfig(handle);
+ statusContext = ocsp_GetCheckingContext(handle);
+ if (statusContext == NULL)
+ return SECFailure;
+
+ if (statusConfig->statusChecker != CERT_CheckOCSPStatus) {
+ /*
+ * Status configuration is present, but either not currently
+ * enabled or not for OCSP.
+ */
+ PORT_SetError(SEC_ERROR_OCSP_NOT_ENABLED);
+ 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.
+ */
+ statusConfig->statusChecker = NULL;
+
+ return SECSuccess;
+}
+
+/*
+ * Allocate and initialize the informational structures for status checking.
+ * This is done when some configuration of OCSP is being done or when OCSP
+ * checking is being turned on, whichever comes first.
+ */
+static SECStatus
+ocsp_InitStatusChecking(CERTCertDBHandle *handle)
+{
+ CERTStatusConfig *statusConfig = NULL;
+ ocspCheckingContext *statusContext = NULL;
+
+ PORT_Assert(CERT_GetStatusConfig(handle) == NULL);
+ if (CERT_GetStatusConfig(handle) != NULL) {
+ /* XXX or call statusConfig->statusDestroy and continue? */
+ return SECFailure;
+ }
+
+ statusConfig = PORT_ZNew(CERTStatusConfig);
+ if (statusConfig == NULL)
+ goto loser;
+
+ statusContext = PORT_ZNew(ocspCheckingContext);
+ if (statusContext == NULL)
+ goto loser;
+
+ statusConfig->statusDestroy = ocsp_DestroyStatusChecking;
+ statusConfig->statusContext = statusContext;
+
+ CERT_SetStatusConfig(handle, statusConfig);
+
+ return SECSuccess;
+
+loser:
+ if (statusConfig != NULL)
+ PORT_Free(statusConfig);
+ return SECFailure;
+}
+
+/*
+ * FUNCTION: CERT_EnableOCSPChecking
+ * Turns on OCSP checking for the given certificate database.
+ * INPUTS:
+ * CERTCertDBHandle *handle
+ * Certificate database for which OCSP checking will be enabled.
+ * RETURN:
+ * Returns SECFailure if an error occurred (likely only problem
+ * allocating memory); SECSuccess otherwise.
+ */
+SECStatus
+CERT_EnableOCSPChecking(CERTCertDBHandle *handle)
+{
+ CERTStatusConfig *statusConfig;
+
+ SECStatus rv;
+
+ if (handle == NULL) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+
+ statusConfig = CERT_GetStatusConfig(handle);
+ if (statusConfig == NULL) {
+ rv = ocsp_InitStatusChecking(handle);
+ if (rv != SECSuccess)
+ return rv;
+
+ /* Get newly established value */
+ statusConfig = CERT_GetStatusConfig(handle);
+ PORT_Assert(statusConfig != NULL);
+ }
+
+ /*
+ * Setting the checker function is what really enables the checking
+ * when each cert verification is done.
+ */
+ statusConfig->statusChecker = CERT_CheckOCSPStatus;
+
+ return SECSuccess;
+}
+
+/*
+ * FUNCTION: CERT_SetOCSPDefaultResponder
+ * Specify the location and cert of the default responder.
+ * If OCSP checking is already enabled *and* use of a default responder
+ * is also already enabled, all OCSP checking from now on will go directly
+ * to the specified responder. If OCSP checking is not enabled, or if
+ * it is but use of a default responder is not enabled, the information
+ * will be recorded and take effect whenever both are enabled.
+ * INPUTS:
+ * CERTCertDBHandle *handle
+ * Cert database on which OCSP checking should use the default responder.
+ * char *url
+ * The location of the default responder (e.g. "http://foo.com:80/ocsp")
+ * Note that the location will not be tested until the first attempt
+ * to send a request there.
+ * char *name
+ * The nickname of the cert to trust (expected) to sign the OCSP responses.
+ * If the corresponding cert cannot be found, SECFailure is returned.
+ * RETURN:
+ * Returns SECFailure if an error occurred; SECSuccess otherwise.
+ * The most likely error is that the cert for "name" could not be found
+ * (probably SEC_ERROR_UNKNOWN_CERT). Other errors are low-level (no memory,
+ * bad database, etc.).
+ */
+SECStatus
+CERT_SetOCSPDefaultResponder(CERTCertDBHandle *handle,
+ const char *url, const char *name)
+{
+ CERTCertificate *cert;
+ ocspCheckingContext *statusContext;
+ char *url_copy = NULL;
+ char *name_copy = NULL;
+ SECStatus rv;
+
+ if (handle == NULL || url == NULL || name == NULL) {
+ /*
+ * XXX When interface is exported, probably want better errors;
+ * perhaps different one for each parameter.
+ */
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+
+ /*
+ * Find the certificate for the specified nickname. Do this first
+ * because it seems the most likely to fail.
+ *
+ * XXX Shouldn't need that cast if the FindCertByNickname interface
+ * used const to convey that it does not modify the name. Maybe someday.
+ */
+ cert = CERT_FindCertByNickname(handle, (char *)name);
+ if (cert == NULL) {
+ /*
+ * look for the cert on an external token.
+ */
+ cert = PK11_FindCertFromNickname((char *)name, NULL);
+ }
+ if (cert == NULL)
+ return SECFailure;
+
+ /*
+ * Make a copy of the url and nickname.
+ */
+ url_copy = PORT_Strdup(url);
+ name_copy = PORT_Strdup(name);
+ if (url_copy == NULL || name_copy == NULL) {
+ rv = SECFailure;
+ goto loser;
+ }
+
+ statusContext = ocsp_GetCheckingContext(handle);
+
+ /*
+ * Allocate and init the context if it doesn't already exist.
+ */
+ if (statusContext == NULL) {
+ rv = ocsp_InitStatusChecking(handle);
+ if (rv != SECSuccess)
+ goto loser;
+
+ statusContext = ocsp_GetCheckingContext(handle);
+ PORT_Assert(statusContext != NULL); /* extreme paranoia */
+ }
+
+ /*
+ * Note -- we do not touch the status context until after all of
+ * the steps which could cause errors. If something goes wrong,
+ * we want to leave things as they were.
+ */
+
+ /*
+ * Get rid of old url and name if there.
+ */
+ if (statusContext->defaultResponderNickname != NULL)
+ PORT_Free(statusContext->defaultResponderNickname);
+ if (statusContext->defaultResponderURI != NULL)
+ PORT_Free(statusContext->defaultResponderURI);
+
+ /*
+ * And replace them with the new ones.
+ */
+ statusContext->defaultResponderURI = url_copy;
+ statusContext->defaultResponderNickname = name_copy;
+
+ /*
+ * If there was already a cert in place, get rid of it and replace it.
+ * Otherwise, we are not currently enabled, so we don't want to save it;
+ * it will get re-found and set whenever use of a default responder is
+ * enabled.
+ */
+ 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;
+
+loser:
+ CERT_DestroyCertificate(cert);
+ if (url_copy != NULL)
+ PORT_Free(url_copy);
+ if (name_copy != NULL)
+ PORT_Free(name_copy);
+ return rv;
+}
+
+/*
+ * FUNCTION: CERT_EnableOCSPDefaultResponder
+ * Turns on use of a default responder when OCSP checking.
+ * If OCSP checking is already enabled, this will make subsequent checks
+ * go directly to the default responder. (The location of the responder
+ * and the nickname of the responder cert must already be specified.)
+ * If OCSP checking is not enabled, this will be recorded and take effect
+ * whenever it is enabled.
+ * INPUTS:
+ * CERTCertDBHandle *handle
+ * Cert database on which OCSP checking should use the default responder.
+ * RETURN:
+ * Returns SECFailure if an error occurred; SECSuccess otherwise.
+ * No errors are especially likely unless the caller did not previously
+ * perform a successful call to SetOCSPDefaultResponder (in which case
+ * the error set will be SEC_ERROR_OCSP_NO_DEFAULT_RESPONDER).
+ */
+SECStatus
+CERT_EnableOCSPDefaultResponder(CERTCertDBHandle *handle)
+{
+ ocspCheckingContext *statusContext;
+ CERTCertificate *cert;
+ SECStatus rv;
+ SECCertificateUsage usage;
+
+ if (handle == NULL) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+
+ statusContext = ocsp_GetCheckingContext(handle);
+
+ if (statusContext == NULL) {
+ /*
+ * Strictly speaking, the error already set is "correct",
+ * but cover over it with one more helpful in this context.
+ */
+ PORT_SetError(SEC_ERROR_OCSP_NO_DEFAULT_RESPONDER);
+ return SECFailure;
+ }
+
+ if (statusContext->defaultResponderURI == NULL) {
+ PORT_SetError(SEC_ERROR_OCSP_NO_DEFAULT_RESPONDER);
+ return SECFailure;
+ }
+
+ if (statusContext->defaultResponderNickname == NULL) {
+ PORT_SetError(SEC_ERROR_OCSP_NO_DEFAULT_RESPONDER);
+ return SECFailure;
+ }
+
+ /*
+ * Find the cert for the nickname.
+ */
+ cert = CERT_FindCertByNickname(handle,
+ statusContext->defaultResponderNickname);
+ if (cert == NULL) {
+ cert = PK11_FindCertFromNickname(statusContext->defaultResponderNickname,
+ NULL);
+ }
+ /*
+ * We should never have trouble finding the cert, because its
+ * existence should have been proven by SetOCSPDefaultResponder.
+ */
+ PORT_Assert(cert != NULL);
+ 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 guaranteed 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.
+ */
+ statusContext->useDefaultResponder = PR_TRUE;
+ return SECSuccess;
+}
+
+/*
+ * FUNCTION: CERT_DisableOCSPDefaultResponder
+ * Turns off use of a default responder when OCSP checking.
+ * (Does nothing if use of a default responder is not enabled.)
+ * INPUTS:
+ * CERTCertDBHandle *handle
+ * Cert database on which OCSP checking should stop using a default
+ * responder.
+ * RETURN:
+ * Returns SECFailure if an error occurred; SECSuccess otherwise.
+ * Errors very unlikely (like random memory corruption...).
+ */
+SECStatus
+CERT_DisableOCSPDefaultResponder(CERTCertDBHandle *handle)
+{
+ CERTStatusConfig *statusConfig;
+ ocspCheckingContext *statusContext;
+ CERTCertificate *tmpCert;
+
+ if (handle == NULL) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+
+ statusConfig = CERT_GetStatusConfig(handle);
+ if (statusConfig == NULL)
+ return SECSuccess;
+
+ statusContext = ocsp_GetCheckingContext(handle);
+ PORT_Assert(statusContext != NULL);
+ if (statusContext == NULL)
+ return SECFailure;
+
+ 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();
+ }
+
+ /*
+ * Finally, record the fact.
+ */
+ statusContext->useDefaultResponder = PR_FALSE;
+ return SECSuccess;
+}
+
+SECStatus
+CERT_ForcePostMethodForOCSP(PRBool forcePost)
+{
+ if (!OCSP_Global.monitor) {
+ PORT_SetError(SEC_ERROR_NOT_INITIALIZED);
+ return SECFailure;
+ }
+
+ PR_EnterMonitor(OCSP_Global.monitor);
+ OCSP_Global.forcePost = forcePost;
+ PR_ExitMonitor(OCSP_Global.monitor);
+
+ return SECSuccess;
+}
+
+SECStatus
+CERT_GetOCSPResponseStatus(CERTOCSPResponse *response)
+{
+ PORT_Assert(response);
+ if (response->statusValue == ocspResponse_successful)
+ return SECSuccess;
+
+ switch (response->statusValue) {
+ case ocspResponse_malformedRequest:
+ PORT_SetError(SEC_ERROR_OCSP_MALFORMED_REQUEST);
+ break;
+ case ocspResponse_internalError:
+ PORT_SetError(SEC_ERROR_OCSP_SERVER_ERROR);
+ break;
+ case ocspResponse_tryLater:
+ PORT_SetError(SEC_ERROR_OCSP_TRY_SERVER_LATER);
+ break;
+ case ocspResponse_sigRequired:
+ /* XXX We *should* retry with a signature, if possible. */
+ PORT_SetError(SEC_ERROR_OCSP_REQUEST_NEEDS_SIG);
+ break;
+ case ocspResponse_unauthorized:
+ PORT_SetError(SEC_ERROR_OCSP_UNAUTHORIZED_REQUEST);
+ break;
+ case ocspResponse_unused:
+ default:
+ PORT_SetError(SEC_ERROR_OCSP_UNKNOWN_RESPONSE_STATUS);
+ break;
+ }
+ return SECFailure;
+}