Index: nss/lib/certhigh/ocsp.c |
=================================================================== |
--- nss/lib/certhigh/ocsp.c (revision 195639) |
+++ nss/lib/certhigh/ocsp.c (working copy) |
@@ -124,9 +124,9 @@ |
CERTCertificate *cert, |
int64 time, |
void *pwArg, |
- SECItem *encodedResponse, |
+ const SECItem *encodedResponse, |
+ PRBool cacheInvalid, |
PRBool *certIDWasConsumed, |
- PRBool cacheNegative, |
SECStatus *rv_ocsp); |
static SECStatus |
@@ -140,6 +140,9 @@ |
static SECStatus |
ocsp_CertRevokedAfter(ocspRevokedInfo *revokedInfo, int64 time); |
+static CERTOCSPCertID * |
+cert_DupOCSPCertID(CERTOCSPCertID *src); |
wtc
2013/04/24 22:49:45
The argument should be 'const'.
|
+ |
#ifndef DEBUG |
#define OCSP_TRACE(msg) |
#define OCSP_TRACE_TIME(msg, time) |
@@ -766,6 +769,9 @@ |
/* |
* 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, |
@@ -777,10 +783,7 @@ |
OCSPCacheItem *cacheItem; |
OCSP_TRACE(("OCSP ocsp_CreateOrUpdateCacheEntry\n")); |
- if (!certIDWasConsumed) { |
- PORT_SetError(SEC_ERROR_INVALID_ARGS); |
- return SECFailure; |
- } |
+ if (certIDWasConsumed) |
*certIDWasConsumed = PR_FALSE; |
wtc
2013/04/24 22:49:45
We should fix the indentation.
|
PR_EnterMonitor(OCSP_Global.monitor); |
@@ -788,23 +791,47 @@ |
cacheItem = ocsp_FindCacheEntry(cache, certID); |
if (!cacheItem) { |
- rv = ocsp_CreateCacheItemAndConsumeCertID(cache, certID, |
+ 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; |
} |
- *certIDWasConsumed = PR_TRUE; |
} |
if (single) { |
- rv = ocsp_SetCacheItemResponse(cacheItem, single); |
- if (rv != SECSuccess) { |
- ocsp_RemoveCacheItem(cache, cacheItem); |
- PR_ExitMonitor(OCSP_Global.monitor); |
- return rv; |
+ 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")); |
wtc
2013/04/24 22:49:45
Fold this long line.
|
} |
} else { |
cacheItem->missingResponseError = PORT_GetError(); |
+ if (cacheItem->certStatusArena) { |
+ PORT_FreeArena(cacheItem->certStatusArena, PR_FALSE); |
+ cacheItem->certStatusArena = NULL; |
+ } |
} |
ocsp_FreshenCacheItemNextFetchAttemptTime(cacheItem); |
ocsp_CheckCacheSize(cache); |
@@ -1545,7 +1572,7 @@ |
* results in a NULL being returned (and an appropriate error set). |
*/ |
-static SECItem * |
+SECItem * |
ocsp_DigestValue(PRArenaPool *arena, SECOidTag digestAlg, |
SECItem *fill, const SECItem *src) |
{ |
@@ -1752,6 +1779,54 @@ |
return certID; |
} |
+static CERTOCSPCertID * |
+cert_DupOCSPCertID(CERTOCSPCertID *src) |
+{ |
+ CERTOCSPCertID *dest; |
+ PRArenaPool *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) |
wtc
2013/04/24 22:49:45
Fix indentation.
|
+ goto loser; |
+ |
+#define DUPHELP(element) \ |
+ if (src->element.data) { \ |
+ if (SECITEM_CopyItem(arena, &dest->element, &src->element) \ |
+ != SECSuccess) \ |
+ goto loser; \ |
wtc
2013/04/24 22:49:45
Add curly braces. Combine nested ifs using &&.
|
+ } |
+ |
+ 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 |
*/ |
@@ -2535,7 +2610,7 @@ |
* or a low-level or internal error occurred). |
*/ |
CERTOCSPResponse * |
-CERT_DecodeOCSPResponse(SECItem *src) |
+CERT_DecodeOCSPResponse(const SECItem *src) |
{ |
PRArenaPool *arena = NULL; |
CERTOCSPResponse *response = NULL; |
@@ -4817,15 +4892,58 @@ |
CERT_CacheOCSPResponseFromSideChannel(CERTCertDBHandle *handle, |
CERTCertificate *cert, |
int64 time, |
- SECItem *encodedResponse, |
+ const SECItem *encodedResponse, |
void *pwArg) |
{ |
- CERTOCSPCertID *certID; |
+ CERTOCSPCertID *certID = NULL; |
PRBool certIDWasConsumed = PR_FALSE; |
SECStatus rv = SECFailure; |
SECStatus rvOcsp; |
SECErrorCodes dummy_error_code; /* we ignore this */ |
+ /* 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) { |
+ PORT_SetError(SEC_ERROR_INVALID_ARGS); |
+ return SECFailure; |
+ } |
certID = CERT_CreateOCSPCertID(cert, time); |
if (!certID) |
return SECFailure; |
@@ -4833,22 +4951,18 @@ |
certID, time, PR_FALSE, /* ignoreGlobalOcspFailureSetting */ |
&rvOcsp, &dummy_error_code); |
if (rv == SECSuccess && rvOcsp == SECSuccess) { |
- /* The cached value is good. We don't want to waste time validating |
- * this OCSP response. */ |
+ /* 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; |
} |
- /* Since the OCSP response came from a side channel it is attacker |
- * controlled. The attacker can have chosen any valid OCSP response, |
- * including responses from the past. In this case, |
- * ocsp_GetVerifiedSingleResponseForCertID will fail. If we recorded a |
- * negative cache entry in this case, then the attacker would have |
- * 'poisoned' our cache (denial of service), so we don't record negative |
- * results. */ |
- rv = ocsp_CacheEncodedOCSPResponse(handle, certID, cert, time, pwArg, |
- encodedResponse, &certIDWasConsumed, |
- PR_FALSE /* don't cache failures */, |
+ /* The logic for caching the more recent response is handled in |
+ * ocsp_CreateOrUpdateCacheEntry, which is called by this function. */ |
+ rv = ocsp_CacheEncodedOCSPResponse(handle, certID, cert, time, |
+ pwArg, encodedResponse, |
+ PR_FALSE /* don't cache if invalid */, |
+ &certIDWasConsumed, |
&rvOcsp); |
if (!certIDWasConsumed) { |
CERT_DestroyOCSPCertID(certID); |
@@ -4936,8 +5050,9 @@ |
} |
rv = ocsp_CacheEncodedOCSPResponse(handle, certID, cert, time, pwArg, |
- encodedResponse, certIDWasConsumed, |
- PR_TRUE /* cache failures */, rv_ocsp); |
+ encodedResponse, |
+ PR_TRUE /* cache if invalid */, |
+ certIDWasConsumed, rv_ocsp); |
loser: |
if (request != NULL) |
@@ -4975,6 +5090,9 @@ |
* the opaque argument to the password prompting function. |
* SECItem *encodedResponse |
* the DER encoded bytes of the OCSP response |
+ * PRBool cacheInvalid |
+ * If true then invalid responses will cause a negative cache entry to be |
+ * created. (Invalid means bad syntax, bad signature etc) |
* PRBool *certIDWasConsumed |
* (output) on return, this is true iff |certID| was consumed by this |
* function. |
@@ -4990,9 +5108,9 @@ |
CERTCertificate *cert, |
int64 time, |
void *pwArg, |
- SECItem *encodedResponse, |
+ const SECItem *encodedResponse, |
+ PRBool cacheInvalid, |
PRBool *certIDWasConsumed, |
- PRBool cacheNegative, |
SECStatus *rv_ocsp) |
{ |
CERTOCSPResponse *response = NULL; |
@@ -5051,7 +5169,8 @@ |
*rv_ocsp = ocsp_SingleResponseCertHasGoodStatus(single, time); |
loser: |
- if (cacheNegative || *rv_ocsp == SECSuccess) { |
+ /* If single == NULL here then the response was invalid. */ |
+ if (single != NULL || cacheInvalid) { |
PR_EnterMonitor(OCSP_Global.monitor); |
if (OCSP_Global.maxCacheEntries >= 0) { |
/* single == NULL means: remember response failure */ |