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