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