mirror of
https://github.com/rn10950/RetroZilla.git
synced 2024-11-14 03:30:17 +01:00
44b7f056d9
bug1001332, 56b691c003ad, bug1086145, bug1054069, bug1155922, bug991783, bug1125025, bug1162521, bug1162644, bug1132941, bug1164364, bug1166205, bug1166163, bug1166515, bug1138554, bug1167046, bug1167043, bug1169451, bug1172128, bug1170322, bug102794, bug1128184, bug557830, bug1174648, bug1180244, bug1177784, bug1173413, bug1169174, bug1084669, bug951455, bug1183395, bug1177430, bug1183827, bug1160139, bug1154106, bug1142209, bug1185033, bug1193467, bug1182667(with sha512 changes backed out, which breaks VC6 compilation), bug1158489, bug337796
6188 lines
190 KiB
C
6188 lines
190 KiB
C
/* 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_BEOS) \
|
|
|| 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 = getenv("NSS_TRACE_OCSP");
|
|
if (ev && ev[0]) {
|
|
wantTrace = PR_TRUE;
|
|
}
|
|
firstTime = PR_FALSE;
|
|
}
|
|
#endif
|
|
return wantTrace;
|
|
}
|
|
|
|
static void
|
|
ocsp_Trace(const char *format, ...)
|
|
{
|
|
char buf[2000];
|
|
va_list args;
|
|
|
|
if (!wantOcspTrace())
|
|
return;
|
|
va_start(args, format);
|
|
PR_vsnprintf(buf, sizeof(buf), format, args);
|
|
va_end(args);
|
|
PR_LogPrint("%s", buf);
|
|
}
|
|
|
|
static void
|
|
ocsp_dumpStringWithTime(const char *str, 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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
/*
|
|
* 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)) {
|
|
rv = SECSuccess;
|
|
} else {
|
|
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;
|
|
}
|