Index: nss/lib/certhigh/ocsp.c |
=================================================================== |
--- nss/lib/certhigh/ocsp.c (revision 239365) |
+++ nss/lib/certhigh/ocsp.c (working copy) |
@@ -24,6 +24,7 @@ |
#include "hasht.h" |
#include "sechash.h" |
#include "secasn1.h" |
+#include "plbase64.h" |
#include "keyhi.h" |
#include "cryptohi.h" |
#include "ocsp.h" |
@@ -86,6 +87,7 @@ |
OCSPCacheData cache; |
SEC_OcspFailureMode ocspFailureMode; |
CERT_StringFromCertFcn alternateOCSPAIAFcn; |
+ PRBool forcePost; |
} OCSP_Global = { NULL, |
NULL, |
DEFAULT_OCSP_CACHE_SIZE, |
@@ -94,7 +96,8 @@ |
DEFAULT_OSCP_TIMEOUT_SECONDS, |
{NULL, 0, NULL, NULL}, |
ocspMode_FailureIsVerificationFailure, |
- NULL |
+ NULL, |
+ PR_FALSE |
}; |
@@ -103,7 +106,9 @@ |
static SECItem * |
ocsp_GetEncodedOCSPResponseFromRequest(PLArenaPool *arena, |
CERTOCSPRequest *request, |
- const char *location, PRTime time, |
+ const char *location, |
+ const char *method, |
+ PRTime time, |
PRBool addServiceLocator, |
void *pwArg, |
CERTOCSPRequest **pRequest); |
@@ -117,25 +122,16 @@ |
SECStatus *rv_ocsp); |
static SECStatus |
-ocsp_CacheEncodedOCSPResponse(CERTCertDBHandle *handle, |
- CERTOCSPCertID *certID, |
- CERTCertificate *cert, |
- PRTime time, |
- void *pwArg, |
- const SECItem *encodedResponse, |
- PRBool cacheInvalid, |
- PRBool *certIDWasConsumed, |
- SECStatus *rv_ocsp); |
+ocsp_GetDecodedVerifiedSingleResponseForID(CERTCertDBHandle *handle, |
+ CERTOCSPCertID *certID, |
+ CERTCertificate *cert, |
+ PRTime time, |
+ void *pwArg, |
+ const SECItem *encodedResponse, |
+ CERTOCSPResponse **pDecodedResponse, |
+ CERTOCSPSingleResponse **pSingle); |
static SECStatus |
-ocsp_GetVerifiedSingleResponseForCertID(CERTCertDBHandle *handle, |
- CERTOCSPResponse *response, |
- CERTOCSPCertID *certID, |
- CERTCertificate *signerCert, |
- PRTime time, |
- CERTOCSPSingleResponse **pSingleResponse); |
- |
-static SECStatus |
ocsp_CertRevokedAfter(ocspRevokedInfo *revokedInfo, PRTime time); |
static CERTOCSPCertID * |
@@ -754,14 +750,23 @@ |
ocsp_IsCacheItemFresh(OCSPCacheItem *cacheItem) |
{ |
PRTime now; |
- PRBool retval; |
+ PRBool fresh; |
- PR_EnterMonitor(OCSP_Global.monitor); |
now = PR_Now(); |
- retval = (cacheItem->nextFetchAttemptTime > now); |
- OCSP_TRACE(("OCSP ocsp_IsCacheItemFresh: %d\n", retval)); |
- PR_ExitMonitor(OCSP_Global.monitor); |
- return retval; |
+ |
+ 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; |
} |
/* |
@@ -788,6 +793,19 @@ |
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) { |
@@ -1460,15 +1478,12 @@ |
CERT_EncodeOCSPRequest(PLArenaPool *arena, CERTOCSPRequest *request, |
void *pwArg) |
{ |
- ocspTBSRequest *tbsRequest; |
SECStatus rv; |
/* XXX All of these should generate errors if they fail. */ |
PORT_Assert(request); |
PORT_Assert(request->tbsRequest); |
- tbsRequest = request->tbsRequest; |
- |
if (request->tbsRequest->extensionHandle != NULL) { |
rv = CERT_FinishExtensions(request->tbsRequest->extensionHandle); |
request->tbsRequest->extensionHandle = NULL; |
@@ -1636,8 +1651,8 @@ |
* results in a NULL being returned (and an appropriate error set). |
*/ |
SECItem * |
-CERT_GetSPKIDigest(PLArenaPool *arena, const CERTCertificate *cert, |
- SECOidTag digestAlg, SECItem *fill) |
+CERT_GetSubjectPublicKeyDigest(PLArenaPool *arena, const CERTCertificate *cert, |
+ SECOidTag digestAlg, SECItem *fill) |
{ |
SECItem spk; |
@@ -1655,9 +1670,9 @@ |
/* |
* Digest the cert's subject name using the specified algorithm. |
*/ |
-static SECItem * |
-cert_GetSubjectNameDigest(PLArenaPool *arena, const CERTCertificate *cert, |
- SECOidTag digestAlg, SECItem *fill) |
+SECItem * |
+CERT_GetSubjectNameDigest(PLArenaPool *arena, const CERTCertificate *cert, |
+ SECOidTag digestAlg, SECItem *fill) |
{ |
SECItem name; |
@@ -1706,36 +1721,36 @@ |
goto loser; |
} |
- if (cert_GetSubjectNameDigest(arena, issuerCert, SEC_OID_SHA1, |
+ 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, |
+ if (CERT_GetSubjectNameDigest(arena, issuerCert, SEC_OID_MD5, |
&(certID->issuerMD5NameHash)) == NULL) { |
goto loser; |
} |
- if (cert_GetSubjectNameDigest(arena, issuerCert, SEC_OID_MD2, |
+ if (CERT_GetSubjectNameDigest(arena, issuerCert, SEC_OID_MD2, |
&(certID->issuerMD2NameHash)) == NULL) { |
goto loser; |
} |
- if (CERT_GetSPKIDigest(arena, issuerCert, SEC_OID_SHA1, |
- &(certID->issuerKeyHash)) == NULL) { |
+ 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_GetSPKIDigest(arena, issuerCert, SEC_OID_MD5, |
- &(certID->issuerMD5KeyHash)) == NULL) { |
+ if (CERT_GetSubjectPublicKeyDigest(arena, issuerCert, SEC_OID_MD5, |
+ &certID->issuerMD5KeyHash) == NULL) { |
goto loser; |
} |
- if (CERT_GetSPKIDigest(arena, issuerCert, SEC_OID_MD2, |
- &(certID->issuerMD2KeyHash)) == NULL) { |
+ if (CERT_GetSubjectPublicKeyDigest(arena, issuerCert, SEC_OID_MD2, |
+ &certID->issuerMD2KeyHash) == NULL) { |
goto loser; |
} |
@@ -2980,6 +2995,12 @@ |
* 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) |
@@ -3012,25 +3033,41 @@ |
PR_snprintf(portstr, sizeof(portstr), ":%d", port); |
} |
- 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; |
+ 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; |
+ /* |
+ * 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; |
- if (PR_Write(sock, encodedRequest->data, |
- (PRInt32) encodedRequest->len) < 0) |
- 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; |
@@ -3338,6 +3375,13 @@ |
*/ |
#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, |
@@ -3381,14 +3425,15 @@ |
pServerSession, |
"http", |
path, |
- "POST", |
+ encodedRequest ? "POST" : "GET", |
PR_TicksPerSecond() * OCSP_Global.timeoutSeconds, |
&pRequestSession) != SECSuccess) { |
PORT_SetError(SEC_ERROR_OCSP_SERVER_ERROR); |
goto loser; |
} |
- if ((*hcv1->setPostDataFcn)( |
+ if (encodedRequest && |
+ (*hcv1->setPostDataFcn)( |
pRequestSession, |
(char*)encodedRequest->data, |
encodedRequest->len, |
@@ -3444,7 +3489,7 @@ |
} |
/* |
- * FUNCTION: CERT_GetEncodedOCSPResponse |
+ * FUNCTION: CERT_GetEncodedOCSPResponseByMethod |
* Creates and sends a request to an OCSP responder, then reads and |
* returns the (encoded) response. |
* INPUTS: |
@@ -3462,6 +3507,11 @@ |
* 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 |
@@ -3490,26 +3540,114 @@ |
* Other errors are low-level problems (no memory, bad database, etc.). |
*/ |
SECItem * |
-CERT_GetEncodedOCSPResponse(PLArenaPool *arena, CERTCertList *certList, |
- const char *location, PRTime time, |
- PRBool addServiceLocator, |
- CERTCertificate *signerCert, void *pwArg, |
- CERTOCSPRequest **pRequest) |
+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, |
- time, addServiceLocator, |
+ 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, PRTime time, |
+ const char *location, |
+ const char *method, |
+ PRTime time, |
PRBool addServiceLocator, |
void *pwArg, |
CERTOCSPRequest **pRequest) |
@@ -3518,6 +3656,9 @@ |
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) |
@@ -3527,7 +3668,15 @@ |
if (encodedRequest == NULL) |
goto loser; |
- encodedResponse = CERT_PostOCSPRequest(arena, location, encodedRequest); |
+ 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; |
@@ -3539,14 +3688,90 @@ |
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; |
@@ -3574,7 +3799,9 @@ |
ocsp_GetEncodedOCSPResponseForSingleCert(PLArenaPool *arena, |
CERTOCSPCertID *certID, |
CERTCertificate *singleCert, |
- const char *location, PRTime time, |
+ const char *location, |
+ const char *method, |
+ PRTime time, |
PRBool addServiceLocator, |
void *pwArg, |
CERTOCSPRequest **pRequest) |
@@ -3584,8 +3811,8 @@ |
addServiceLocator, NULL); |
if (!request) |
return NULL; |
- return ocsp_GetEncodedOCSPResponseFromRequest(arena, request, location, |
- time, addServiceLocator, |
+ return ocsp_GetEncodedOCSPResponseFromRequest(arena, request, location, |
+ method, time, addServiceLocator, |
pwArg, pRequest); |
} |
@@ -3676,19 +3903,22 @@ |
item.data = buf; |
item.len = SHA1_LENGTH; |
- if (CERT_GetSPKIDigest(NULL,testCert,SEC_OID_SHA1, &item) == NULL) { |
+ if (CERT_GetSubjectPublicKeyDigest(NULL,testCert,SEC_OID_SHA1, |
+ &item) == NULL) { |
return PR_FALSE; |
} |
if (SECITEM_ItemsAreEqual(certIndex,&item)) { |
return PR_TRUE; |
} |
- if (CERT_GetSPKIDigest(NULL,testCert,SEC_OID_MD5, &item) == NULL) { |
+ if (CERT_GetSubjectPublicKeyDigest(NULL,testCert,SEC_OID_MD5, |
+ &item) == NULL) { |
return PR_FALSE; |
} |
if (SECITEM_ItemsAreEqual(certIndex,&item)) { |
return PR_TRUE; |
} |
- if (CERT_GetSPKIDigest(NULL,testCert,SEC_OID_MD2, &item) == NULL) { |
+ if (CERT_GetSubjectPublicKeyDigest(NULL,testCert,SEC_OID_MD2, |
+ &item) == NULL) { |
return PR_FALSE; |
} |
if (SECITEM_ItemsAreEqual(certIndex,&item)) { |
@@ -3789,6 +4019,9 @@ |
signerCert = CERT_DupCertificate(certs[i]); |
} |
} |
+ if (signerCert == NULL) { |
+ PORT_SetError(SEC_ERROR_UNKNOWN_CERT); |
+ } |
} |
finish: |
@@ -4238,7 +4471,7 @@ |
hashAlg = SECOID_FindOIDTag(&certID->hashAlgorithm.algorithm); |
- keyHash = CERT_GetSPKIDigest(NULL, signerCert, hashAlg, NULL); |
+ keyHash = CERT_GetSubjectPublicKeyDigest(NULL, signerCert, hashAlg, NULL); |
if (keyHash != NULL) { |
keyHashEQ = |
@@ -4247,7 +4480,7 @@ |
SECITEM_FreeItem(keyHash, PR_TRUE); |
} |
if (keyHashEQ && |
- (nameHash = cert_GetSubjectNameDigest(NULL, signerCert, |
+ (nameHash = CERT_GetSubjectNameDigest(NULL, signerCert, |
hashAlg, NULL))) { |
nameHashEQ = |
(SECITEM_CompareItem(nameHash, |
@@ -4285,8 +4518,8 @@ |
return PR_FALSE; |
} |
- keyHash = CERT_GetSPKIDigest(NULL, issuerCert, hashAlg, NULL); |
- nameHash = cert_GetSubjectNameDigest(NULL, issuerCert, hashAlg, NULL); |
+ keyHash = CERT_GetSubjectPublicKeyDigest(NULL, issuerCert, hashAlg, NULL); |
+ nameHash = CERT_GetSubjectNameDigest(NULL, issuerCert, hashAlg, NULL); |
CERT_DestroyCertificate(issuerCert); |
@@ -4672,7 +4905,7 @@ |
* See if the cert represented in the single response had a good status |
* at the specified time. |
*/ |
-static SECStatus |
+SECStatus |
ocsp_CertHasGoodStatus(ocspCertStatus *status, PRTime time) |
{ |
SECStatus rv; |
@@ -4704,7 +4937,7 @@ |
return ocsp_CertHasGoodStatus(single->certStatus, time); |
} |
-/* Return value SECFailure means: not found or not fresh. |
+/* 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, |
@@ -4712,34 +4945,39 @@ |
* 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_GetCachedOCSPResponseStatusIfFresh(CERTOCSPCertID *certID, |
- PRTime time, |
- PRBool ignoreGlobalOcspFailureSetting, |
- SECStatus *rvOcsp, |
- SECErrorCodes *missingResponseError) |
+ocsp_GetCachedOCSPResponseStatus(CERTOCSPCertID *certID, |
+ PRTime time, |
+ PRBool ignoreGlobalOcspFailureSetting, |
+ SECStatus *rvOcsp, |
+ SECErrorCodes *missingResponseError, |
+ OCSPFreshness *cacheFreshness) |
{ |
OCSPCacheItem *cacheItem = NULL; |
- SECStatus rv = SECFailure; |
- if (!certID || !missingResponseError || !rvOcsp) { |
+ 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 && ocsp_IsCacheItemFresh(cacheItem)) { |
+ 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(); |
} |
- rv = SECSuccess; |
} else { |
/* |
* No status cached, the previous attempt failed. |
@@ -4747,17 +4985,17 @@ |
* However, if OCSP is optional, a recent OCSP failure is |
* an allowed good state. |
*/ |
- if (!ignoreGlobalOcspFailureSetting && |
+ if (*cacheFreshness == ocspFresh && |
+ !ignoreGlobalOcspFailureSetting && |
OCSP_Global.ocspFailureMode == |
ocspMode_FailureIsNotAVerificationFailure) { |
- rv = SECSuccess; |
*rvOcsp = SECSuccess; |
} |
*missingResponseError = cacheItem->missingResponseError; |
} |
} |
PR_ExitMonitor(OCSP_Global.monitor); |
- return rv; |
+ return SECSuccess; |
} |
PRBool |
@@ -4828,9 +5066,10 @@ |
{ |
CERTOCSPCertID *certID; |
PRBool certIDWasConsumed = PR_FALSE; |
- SECStatus rv = SECFailure; |
+ SECStatus rv; |
SECStatus rvOcsp; |
- SECErrorCodes dummy_error_code; /* we ignore this */ |
+ SECErrorCodes cachedErrorCode; |
+ OCSPFreshness cachedResponseFreshness; |
OCSP_TRACE_CERT(cert); |
OCSP_TRACE_TIME("## requested validity time:", time); |
@@ -4838,21 +5077,41 @@ |
certID = CERT_CreateOCSPCertID(cert, time); |
if (!certID) |
return SECFailure; |
- rv = ocsp_GetCachedOCSPResponseStatusIfFresh( |
+ rv = ocsp_GetCachedOCSPResponseStatus( |
certID, time, PR_FALSE, /* ignoreGlobalOcspFailureSetting */ |
- &rvOcsp, &dummy_error_code); |
- if (rv == SECSuccess) { |
+ &rvOcsp, &cachedErrorCode, &cachedResponseFreshness); |
+ if (rv != SECSuccess) { |
CERT_DestroyOCSPCertID(certID); |
+ return SECFailure; |
+ } |
+ if (cachedResponseFreshness == ocspFresh) { |
+ CERT_DestroyOCSPCertID(certID); |
return rvOcsp; |
} |
- rv = ocsp_GetOCSPStatusFromNetwork(handle, certID, cert, time, pwArg, |
+ |
+ rv = ocsp_GetOCSPStatusFromNetwork(handle, certID, cert, time, pwArg, |
&certIDWasConsumed, |
&rvOcsp); |
if (rv != SECSuccess) { |
- /* we were unable to obtain ocsp status. Check if we should |
- * return cert status revoked. */ |
- rvOcsp = ocsp_FetchingFailureIsVerificationFailure() ? |
- SECFailure : 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); |
@@ -4898,8 +5157,11 @@ |
CERTOCSPCertID *certID = NULL; |
PRBool certIDWasConsumed = PR_FALSE; |
SECStatus rv = SECFailure; |
- SECStatus rvOcsp; |
+ 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) |
@@ -4940,17 +5202,21 @@ |
* side channel. |
*/ |
- if (!cert) { |
+ if (!cert || !encodedResponse) { |
PORT_SetError(SEC_ERROR_INVALID_ARGS); |
return SECFailure; |
} |
certID = CERT_CreateOCSPCertID(cert, time); |
if (!certID) |
return SECFailure; |
- rv = ocsp_GetCachedOCSPResponseStatusIfFresh( |
- certID, time, PR_FALSE, /* ignoreGlobalOcspFailureSetting */ |
- &rvOcsp, &dummy_error_code); |
- if (rv == SECSuccess && rvOcsp == SECSuccess) { |
+ |
+ /* 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); |
@@ -4958,12 +5224,21 @@ |
} |
/* 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); |
+ * 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); |
} |
@@ -4989,6 +5264,11 @@ |
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; |
@@ -4996,6 +5276,18 @@ |
*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 |
@@ -5041,36 +5333,88 @@ |
* should be passed into this function or retrieved via some operation |
* on the handle/context. |
*/ |
- encodedResponse = |
- ocsp_GetEncodedOCSPResponseForSingleCert(NULL, certID, cert, location, |
- time, locationIsDefault, |
- pwArg, &request); |
- if (encodedResponse == NULL) { |
- goto loser; |
- } |
- rv = ocsp_CacheEncodedOCSPResponse(handle, certID, cert, time, pwArg, |
- encodedResponse, |
- PR_TRUE /* cache if invalid */, |
- certIDWasConsumed, rv_ocsp); |
+ do { |
+ const char *method; |
+ PRBool validResponseWithAccurateInfo = PR_FALSE; |
+ retry = PR_FALSE; |
+ *rv_ocsp = SECFailure; |
-loser: |
- if (request != NULL) |
- CERT_DestroyOCSPRequest(request); |
- if (encodedResponse != NULL) |
- SECITEM_FreeItem(encodedResponse, PR_TRUE); |
- if (location != NULL) |
- PORT_Free(location); |
+ 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_CacheEncodedOCSPResponse |
+ * FUNCTION: ocsp_GetDecodedVerifiedSingleResponseForID |
* This function decodes an OCSP response and checks for a valid response |
- * concerning the given certificate. If such a response is not found |
- * then nothing is cached. Otherwise, if it is a good response, or if |
- * cacheNegative is true, the results are stored in the OCSP cache. |
+ * 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. |
@@ -5090,42 +5434,38 @@ |
* 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. |
- * SECStatus *rv_ocsp |
- * (output) on return, this is SECSuccess iff the response is good (see |
- * definition of 'good' above). |
+ * 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_CacheEncodedOCSPResponse(CERTCertDBHandle *handle, |
- CERTOCSPCertID *certID, |
- CERTCertificate *cert, |
- PRTime time, |
- void *pwArg, |
- const SECItem *encodedResponse, |
- PRBool cacheInvalid, |
- PRBool *certIDWasConsumed, |
- SECStatus *rv_ocsp) |
+ocsp_GetDecodedVerifiedSingleResponseForID(CERTCertDBHandle *handle, |
+ CERTOCSPCertID *certID, |
+ CERTCertificate *cert, |
+ PRTime time, |
+ void *pwArg, |
+ const SECItem *encodedResponse, |
+ CERTOCSPResponse **pDecodedResponse, |
+ CERTOCSPSingleResponse **pSingle) |
{ |
- CERTOCSPResponse *response = NULL; |
CERTCertificate *signerCert = NULL; |
CERTCertificate *issuerCert = NULL; |
- CERTOCSPSingleResponse *single = NULL; |
SECStatus rv = SECFailure; |
- *certIDWasConsumed = PR_FALSE; |
- *rv_ocsp = SECFailure; |
- |
- response = CERT_DecodeOCSPResponse(encodedResponse); |
- if (response == NULL) { |
- goto loser; |
+ 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! |
@@ -5136,7 +5476,7 @@ |
* Otherwise, we continue to find the actual per-cert status |
* in the response. |
*/ |
- if (CERT_GetOCSPResponseStatus(response) != SECSuccess) { |
+ if (CERT_GetOCSPResponseStatus(*pDecodedResponse) != SECSuccess) { |
goto loser; |
} |
@@ -5145,54 +5485,60 @@ |
* So, check for that. |
*/ |
issuerCert = CERT_FindCertIssuer(cert, time, certUsageAnyCA); |
- rv = CERT_VerifyOCSPResponseSignature(response, handle, pwArg, &signerCert, |
- issuerCert); |
- if (rv != SECSuccess) |
+ 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; |
+} |
- rv = ocsp_GetVerifiedSingleResponseForCertID(handle, response, certID, |
- signerCert, time, &single); |
- if (rv != SECSuccess) |
- goto loser; |
- |
- *rv_ocsp = ocsp_SingleResponseCertHasGoodStatus(single, time); |
- |
-loser: |
- /* If single == NULL here then the response was invalid. */ |
- if (single != NULL || cacheInvalid) { |
+/* |
+ * 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) { |
- /* single == NULL means: remember response failure */ |
ocsp_CreateOrUpdateCacheEntry(&OCSP_Global.cache, certID, single, |
certIDWasConsumed); |
/* ignore cache update failures */ |
} |
PR_ExitMonitor(OCSP_Global.monitor); |
} |
- |
- /* 'single' points within the response so there's no need to free it. */ |
- |
- if (issuerCert != NULL) |
- CERT_DestroyCertificate(issuerCert); |
- if (signerCert != NULL) |
- CERT_DestroyCertificate(signerCert); |
- if (response != NULL) |
- CERT_DestroyOCSPResponse(response); |
- return rv; |
} |
-static SECStatus |
+SECStatus |
ocsp_GetVerifiedSingleResponseForCertID(CERTCertDBHandle *handle, |
CERTOCSPResponse *response, |
CERTOCSPCertID *certID, |
@@ -5785,7 +6131,21 @@ |
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) |
{ |