Overlapping fixes for Bug 554369 and Bug 360420. OCSP caching fixes by Adam Langley, r=kaie; Cache injection of OCSP stapling data inside default auth code, by me, r=rrelyea

git-svn-id: svn://10.0.0.236/trunk@264733 18797224-902f-48f8-a5cc-f745e15eee43
This commit is contained in:
kaie%kuix.de 2013-02-15 17:53:24 +00:00
parent 96ed6ee6a5
commit a555bc1567
3 changed files with 170 additions and 40 deletions

View File

@ -6,7 +6,7 @@
* Implementation of OCSP services, for both client and server. * Implementation of OCSP services, for both client and server.
* (XXX, really, mostly just for client right now, but intended to do both.) * (XXX, really, mostly just for client right now, but intended to do both.)
* *
* $Id: ocsp.c,v 1.77 2013-01-23 23:05:50 kaie%kuix.de Exp $ * $Id: ocsp.c,v 1.78 2013-02-15 17:53:24 kaie%kuix.de Exp $
*/ */
#include "prerror.h" #include "prerror.h"
@ -124,9 +124,9 @@ ocsp_CacheEncodedOCSPResponse(CERTCertDBHandle *handle,
CERTCertificate *cert, CERTCertificate *cert,
int64 time, int64 time,
void *pwArg, void *pwArg,
SECItem *encodedResponse, const SECItem *encodedResponse,
PRBool cacheInvalid,
PRBool *certIDWasConsumed, PRBool *certIDWasConsumed,
PRBool cacheNegative,
SECStatus *rv_ocsp); SECStatus *rv_ocsp);
static SECStatus static SECStatus
@ -140,6 +140,9 @@ ocsp_GetVerifiedSingleResponseForCertID(CERTCertDBHandle *handle,
static SECStatus static SECStatus
ocsp_CertRevokedAfter(ocspRevokedInfo *revokedInfo, int64 time); ocsp_CertRevokedAfter(ocspRevokedInfo *revokedInfo, int64 time);
static CERTOCSPCertID *
cert_DupOCSPCertID(CERTOCSPCertID *src);
#ifndef DEBUG #ifndef DEBUG
#define OCSP_TRACE(msg) #define OCSP_TRACE(msg)
#define OCSP_TRACE_TIME(msg, time) #define OCSP_TRACE_TIME(msg, time)
@ -766,6 +769,9 @@ ocsp_IsCacheItemFresh(OCSPCacheItem *cacheItem)
/* /*
* Status in *certIDWasConsumed will always be correct, regardless of * Status in *certIDWasConsumed will always be correct, regardless of
* return value. * 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 static SECStatus
ocsp_CreateOrUpdateCacheEntry(OCSPCacheData *cache, ocsp_CreateOrUpdateCacheEntry(OCSPCacheData *cache,
@ -777,10 +783,7 @@ ocsp_CreateOrUpdateCacheEntry(OCSPCacheData *cache,
OCSPCacheItem *cacheItem; OCSPCacheItem *cacheItem;
OCSP_TRACE(("OCSP ocsp_CreateOrUpdateCacheEntry\n")); OCSP_TRACE(("OCSP ocsp_CreateOrUpdateCacheEntry\n"));
if (!certIDWasConsumed) { if (certIDWasConsumed)
PORT_SetError(SEC_ERROR_INVALID_ARGS);
return SECFailure;
}
*certIDWasConsumed = PR_FALSE; *certIDWasConsumed = PR_FALSE;
PR_EnterMonitor(OCSP_Global.monitor); PR_EnterMonitor(OCSP_Global.monitor);
@ -788,23 +791,47 @@ ocsp_CreateOrUpdateCacheEntry(OCSPCacheData *cache,
cacheItem = ocsp_FindCacheEntry(cache, certID); cacheItem = ocsp_FindCacheEntry(cache, certID);
if (!cacheItem) { 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); &cacheItem);
if (rv != SECSuccess) { if (rv != SECSuccess) {
PR_ExitMonitor(OCSP_Global.monitor); PR_ExitMonitor(OCSP_Global.monitor);
return rv; return rv;
} }
*certIDWasConsumed = PR_TRUE;
} }
if (single) { if (single) {
PRTime thisUpdate;
rv = DER_GeneralizedTimeToTime(&thisUpdate, &single->thisUpdate);
if (!cacheItem->haveThisUpdate ||
(rv == SECSuccess && cacheItem->thisUpdate < thisUpdate)) {
rv = ocsp_SetCacheItemResponse(cacheItem, single); rv = ocsp_SetCacheItemResponse(cacheItem, single);
if (rv != SECSuccess) { if (rv != SECSuccess) {
ocsp_RemoveCacheItem(cache, cacheItem); ocsp_RemoveCacheItem(cache, cacheItem);
PR_ExitMonitor(OCSP_Global.monitor); PR_ExitMonitor(OCSP_Global.monitor);
return rv; return rv;
} }
} else {
OCSP_TRACE(("Not caching response because the response is not newer than the cache"));
}
} else { } else {
cacheItem->missingResponseError = PORT_GetError(); cacheItem->missingResponseError = PORT_GetError();
if (cacheItem->certStatusArena) {
PORT_FreeArena(cacheItem->certStatusArena, PR_FALSE);
cacheItem->certStatusArena = NULL;
}
} }
ocsp_FreshenCacheItemNextFetchAttemptTime(cacheItem); ocsp_FreshenCacheItemNextFetchAttemptTime(cacheItem);
ocsp_CheckCacheSize(cache); ocsp_CheckCacheSize(cache);
@ -1752,6 +1779,54 @@ CERT_CreateOCSPCertID(CERTCertificate *cert, int64 time)
return certID; 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)
goto loser;
#define DUPHELP(element) \
if (src->element.data) { \
if (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 * Callback to set Extensions in request object
*/ */
@ -2535,7 +2610,7 @@ ocsp_DecodeResponseBytes(PRArenaPool *arena, ocspResponseBytes *rbytes)
* or a low-level or internal error occurred). * or a low-level or internal error occurred).
*/ */
CERTOCSPResponse * CERTOCSPResponse *
CERT_DecodeOCSPResponse(SECItem *src) CERT_DecodeOCSPResponse(const SECItem *src)
{ {
PRArenaPool *arena = NULL; PRArenaPool *arena = NULL;
CERTOCSPResponse *response = NULL; CERTOCSPResponse *response = NULL;
@ -4817,15 +4892,58 @@ SECStatus
CERT_CacheOCSPResponseFromSideChannel(CERTCertDBHandle *handle, CERT_CacheOCSPResponseFromSideChannel(CERTCertDBHandle *handle,
CERTCertificate *cert, CERTCertificate *cert,
int64 time, int64 time,
SECItem *encodedResponse, const SECItem *encodedResponse,
void *pwArg) void *pwArg)
{ {
CERTOCSPCertID *certID; CERTOCSPCertID *certID = NULL;
PRBool certIDWasConsumed = PR_FALSE; PRBool certIDWasConsumed = PR_FALSE;
SECStatus rv = SECFailure; SECStatus rv = SECFailure;
SECStatus rvOcsp; SECStatus rvOcsp;
SECErrorCodes dummy_error_code; /* we ignore this */ 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); certID = CERT_CreateOCSPCertID(cert, time);
if (!certID) if (!certID)
return SECFailure; return SECFailure;
@ -4834,21 +4952,17 @@ CERT_CacheOCSPResponseFromSideChannel(CERTCertDBHandle *handle,
&rvOcsp, &dummy_error_code); &rvOcsp, &dummy_error_code);
if (rv == SECSuccess && rvOcsp == SECSuccess) { if (rv == SECSuccess && rvOcsp == SECSuccess) {
/* The cached value is good. We don't want to waste time validating /* The cached value is good. We don't want to waste time validating
* this OCSP response. */ * this OCSP response. This is the first column in the table above. */
CERT_DestroyOCSPCertID(certID); CERT_DestroyOCSPCertID(certID);
return rv; return rv;
} }
/* Since the OCSP response came from a side channel it is attacker /* The logic for caching the more recent response is handled in
* controlled. The attacker can have chosen any valid OCSP response, * ocsp_CreateOrUpdateCacheEntry, which is called by this function. */
* including responses from the past. In this case, rv = ocsp_CacheEncodedOCSPResponse(handle, certID, cert, time,
* ocsp_GetVerifiedSingleResponseForCertID will fail. If we recorded a pwArg, encodedResponse,
* negative cache entry in this case, then the attacker would have PR_FALSE /* don't cache if invalid */,
* 'poisoned' our cache (denial of service), so we don't record negative &certIDWasConsumed,
* results. */
rv = ocsp_CacheEncodedOCSPResponse(handle, certID, cert, time, pwArg,
encodedResponse, &certIDWasConsumed,
PR_FALSE /* don't cache failures */,
&rvOcsp); &rvOcsp);
if (!certIDWasConsumed) { if (!certIDWasConsumed) {
CERT_DestroyOCSPCertID(certID); CERT_DestroyOCSPCertID(certID);
@ -4936,8 +5050,9 @@ ocsp_GetOCSPStatusFromNetwork(CERTCertDBHandle *handle,
} }
rv = ocsp_CacheEncodedOCSPResponse(handle, certID, cert, time, pwArg, rv = ocsp_CacheEncodedOCSPResponse(handle, certID, cert, time, pwArg,
encodedResponse, certIDWasConsumed, encodedResponse,
PR_TRUE /* cache failures */, rv_ocsp); PR_TRUE /* cache if invalid */,
certIDWasConsumed, rv_ocsp);
loser: loser:
if (request != NULL) if (request != NULL)
@ -4975,6 +5090,9 @@ loser:
* the opaque argument to the password prompting function. * the opaque argument to the password prompting function.
* SECItem *encodedResponse * SECItem *encodedResponse
* the DER encoded bytes of the OCSP response * 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 * PRBool *certIDWasConsumed
* (output) on return, this is true iff |certID| was consumed by this * (output) on return, this is true iff |certID| was consumed by this
* function. * function.
@ -4990,9 +5108,9 @@ ocsp_CacheEncodedOCSPResponse(CERTCertDBHandle *handle,
CERTCertificate *cert, CERTCertificate *cert,
int64 time, int64 time,
void *pwArg, void *pwArg,
SECItem *encodedResponse, const SECItem *encodedResponse,
PRBool cacheInvalid,
PRBool *certIDWasConsumed, PRBool *certIDWasConsumed,
PRBool cacheNegative,
SECStatus *rv_ocsp) SECStatus *rv_ocsp)
{ {
CERTOCSPResponse *response = NULL; CERTOCSPResponse *response = NULL;
@ -5051,7 +5169,8 @@ ocsp_CacheEncodedOCSPResponse(CERTCertDBHandle *handle,
*rv_ocsp = ocsp_SingleResponseCertHasGoodStatus(single, time); *rv_ocsp = ocsp_SingleResponseCertHasGoodStatus(single, time);
loser: loser:
if (cacheNegative || *rv_ocsp == SECSuccess) { /* If single == NULL here then the response was invalid. */
if (single != NULL || cacheInvalid) {
PR_EnterMonitor(OCSP_Global.monitor); PR_EnterMonitor(OCSP_Global.monitor);
if (OCSP_Global.maxCacheEntries >= 0) { if (OCSP_Global.maxCacheEntries >= 0) {
/* single == NULL means: remember response failure */ /* single == NULL means: remember response failure */

View File

@ -5,7 +5,7 @@
/* /*
* Interface to the OCSP implementation. * Interface to the OCSP implementation.
* *
* $Id: ocsp.h,v 1.24 2012-12-12 16:03:44 wtc%google.com Exp $ * $Id: ocsp.h,v 1.25 2013-02-15 17:53:24 kaie%kuix.de Exp $
*/ */
#ifndef _OCSP_H_ #ifndef _OCSP_H_
@ -300,7 +300,7 @@ CERT_DestroyOCSPRequest(CERTOCSPRequest *request);
* or a low-level or internal error occurred). * or a low-level or internal error occurred).
*/ */
extern CERTOCSPResponse * extern CERTOCSPResponse *
CERT_DecodeOCSPResponse(SECItem *src); CERT_DecodeOCSPResponse(const SECItem *src);
/* /*
* FUNCTION: CERT_DestroyOCSPResponse * FUNCTION: CERT_DestroyOCSPResponse
@ -551,7 +551,7 @@ extern SECStatus
CERT_CacheOCSPResponseFromSideChannel(CERTCertDBHandle *handle, CERT_CacheOCSPResponseFromSideChannel(CERTCertDBHandle *handle,
CERTCertificate *cert, CERTCertificate *cert,
PRTime time, PRTime time,
SECItem *encodedResponse, const SECItem *encodedResponse,
void *pwArg); void *pwArg);
/* /*

View File

@ -1,13 +1,14 @@
/* This Source Code Form is subject to the terms of the Mozilla Public /* 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 * 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/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* $Id: sslauth.c,v 1.18 2012-04-25 14:50:12 gerv%gerv.net Exp $ */ /* $Id: sslauth.c,v 1.19 2013-02-15 17:53:24 kaie%kuix.de Exp $ */
#include "cert.h" #include "cert.h"
#include "secitem.h" #include "secitem.h"
#include "ssl.h" #include "ssl.h"
#include "sslimpl.h" #include "sslimpl.h"
#include "sslproto.h" #include "sslproto.h"
#include "pk11func.h" #include "pk11func.h"
#include "ocsp.h"
/* NEED LOCKS IN HERE. */ /* NEED LOCKS IN HERE. */
CERTCertificate * CERTCertificate *
@ -214,6 +215,7 @@ SSL_AuthCertificate(void *arg, PRFileDesc *fd, PRBool checkSig, PRBool isServer)
sslSocket * ss; sslSocket * ss;
SECCertUsage certUsage; SECCertUsage certUsage;
const char * hostname = NULL; const char * hostname = NULL;
PRTime now = PR_Now();
ss = ssl_FindSocket(fd); ss = ssl_FindSocket(fd);
PORT_Assert(ss != NULL); PORT_Assert(ss != NULL);
@ -223,11 +225,20 @@ SSL_AuthCertificate(void *arg, PRFileDesc *fd, PRBool checkSig, PRBool isServer)
handle = (CERTCertDBHandle *)arg; handle = (CERTCertDBHandle *)arg;
if (ss->sec.ci.sid->peerCertStatus.len) {
unsigned int i;
CERT_CacheOCSPResponseFromSideChannel(handle,
ss->sec.peerCert,
now,
&ss->sec.ci.sid->peerCertStatus,
arg);
}
/* this may seem backwards, but isn't. */ /* this may seem backwards, but isn't. */
certUsage = isServer ? certUsageSSLClient : certUsageSSLServer; certUsage = isServer ? certUsageSSLClient : certUsageSSLServer;
rv = CERT_VerifyCertNow(handle, ss->sec.peerCert, checkSig, certUsage, rv = CERT_VerifyCert(handle, ss->sec.peerCert, checkSig, certUsage,
ss->pkcs11PinArg); now, ss->pkcs11PinArg, NULL);
if ( rv != SECSuccess || isServer ) if ( rv != SECSuccess || isServer )
return rv; return rv;