diff options
Diffstat (limited to 'security/nss/lib/certhigh/ocsp.c')
-rw-r--r-- | security/nss/lib/certhigh/ocsp.c | 6119 |
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, + ®isteredHttpClient->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; +} |