/* 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);
--> --------------------
--> maximum size reached
--> --------------------