| /* 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/. */ |
| /* |
| * pkix_ocspchecker.c |
| * |
| * OcspChecker Object Functions |
| * |
| */ |
| |
| #include "pkix_ocspchecker.h" |
| #include "pkix_pl_ocspcertid.h" |
| #include "pkix_error.h" |
| |
| |
| /* --Private-Data-and-Types--------------------------------------- */ |
| |
| typedef struct pkix_OcspCheckerStruct { |
| /* RevocationMethod is the super class of OcspChecker. */ |
| pkix_RevocationMethod method; |
| PKIX_PL_VerifyCallback certVerifyFcn; |
| } pkix_OcspChecker; |
| |
| /* --Private-Functions-------------------------------------------- */ |
| |
| /* |
| * FUNCTION: pkix_OcspChecker_Destroy |
| * (see comments for PKIX_PL_DestructorCallback in pkix_pl_system.h) |
| */ |
| static PKIX_Error * |
| pkix_OcspChecker_Destroy( |
| PKIX_PL_Object *object, |
| void *plContext) |
| { |
| return NULL; |
| } |
| |
| /* |
| * FUNCTION: pkix_OcspChecker_RegisterSelf |
| * DESCRIPTION: |
| * Registers PKIX_OCSPCHECKER_TYPE and its related functions with |
| * systemClasses[] |
| * THREAD SAFETY: |
| * Not Thread Safe - for performance and complexity reasons |
| * |
| * Since this function is only called by PKIX_PL_Initialize, which should |
| * only be called once, it is acceptable that this function is not |
| * thread-safe. |
| */ |
| PKIX_Error * |
| pkix_OcspChecker_RegisterSelf(void *plContext) |
| { |
| extern pkix_ClassTable_Entry systemClasses[PKIX_NUMTYPES]; |
| pkix_ClassTable_Entry* entry = &systemClasses[PKIX_OCSPCHECKER_TYPE]; |
| |
| PKIX_ENTER(OCSPCHECKER, "pkix_OcspChecker_RegisterSelf"); |
| |
| entry->description = "OcspChecker"; |
| entry->typeObjectSize = sizeof(pkix_OcspChecker); |
| entry->destructor = pkix_OcspChecker_Destroy; |
| |
| PKIX_RETURN(OCSPCHECKER); |
| } |
| |
| |
| /* |
| * FUNCTION: pkix_OcspChecker_Create |
| */ |
| PKIX_Error * |
| pkix_OcspChecker_Create(PKIX_RevocationMethodType methodType, |
| PKIX_UInt32 flags, |
| PKIX_UInt32 priority, |
| pkix_LocalRevocationCheckFn localRevChecker, |
| pkix_ExternalRevocationCheckFn externalRevChecker, |
| PKIX_PL_VerifyCallback verifyFn, |
| pkix_RevocationMethod **pChecker, |
| void *plContext) |
| { |
| pkix_OcspChecker *method = NULL; |
| |
| PKIX_ENTER(OCSPCHECKER, "pkix_OcspChecker_Create"); |
| PKIX_NULLCHECK_ONE(pChecker); |
| |
| PKIX_CHECK(PKIX_PL_Object_Alloc |
| (PKIX_OCSPCHECKER_TYPE, |
| sizeof (pkix_OcspChecker), |
| (PKIX_PL_Object **)&method, |
| plContext), |
| PKIX_COULDNOTCREATECERTCHAINCHECKEROBJECT); |
| |
| pkixErrorResult = pkix_RevocationMethod_Init( |
| (pkix_RevocationMethod*)method, methodType, flags, priority, |
| localRevChecker, externalRevChecker, plContext); |
| if (pkixErrorResult) { |
| goto cleanup; |
| } |
| method->certVerifyFcn = (PKIX_PL_VerifyCallback)verifyFn; |
| |
| *pChecker = (pkix_RevocationMethod*)method; |
| method = NULL; |
| |
| cleanup: |
| PKIX_DECREF(method); |
| |
| PKIX_RETURN(OCSPCHECKER); |
| } |
| |
| /* |
| * FUNCTION: pkix_OcspChecker_MapResultCodeToRevStatus |
| */ |
| PKIX_RevocationStatus |
| pkix_OcspChecker_MapResultCodeToRevStatus(SECErrorCodes resultCode) |
| { |
| switch (resultCode) { |
| case SEC_ERROR_REVOKED_CERTIFICATE: |
| return PKIX_RevStatus_Revoked; |
| default: |
| return PKIX_RevStatus_NoInfo; |
| } |
| } |
| |
| /* --Public-Functions--------------------------------------------- */ |
| |
| /* |
| * FUNCTION: pkix_OcspChecker_Check (see comments in pkix_checker.h) |
| */ |
| |
| /* |
| * The OCSPChecker is created in an idle state, and remains in this state until |
| * either (a) the default Responder has been set and enabled, and a Check |
| * request is received with no responder specified, or (b) a Check request is |
| * received with a specified responder. A request message is constructed and |
| * given to the HttpClient. If non-blocking I/O is used the client may return |
| * with WOULDBLOCK, in which case the OCSPChecker returns the WOULDBLOCK |
| * condition to its caller in turn. On a subsequent call the I/O is resumed. |
| * When a response is received it is decoded and the results provided to the |
| * caller. |
| * |
| */ |
| PKIX_Error * |
| pkix_OcspChecker_CheckLocal( |
| PKIX_PL_Cert *cert, |
| PKIX_PL_Cert *issuer, |
| PKIX_PL_Date *date, |
| pkix_RevocationMethod *checkerObject, |
| PKIX_ProcessingParams *procParams, |
| PKIX_UInt32 methodFlags, |
| PKIX_Boolean chainVerificationState, |
| PKIX_RevocationStatus *pRevStatus, |
| CERTCRLEntryReasonCode *pReasonCode, |
| void *plContext) |
| { |
| PKIX_PL_OcspCertID *cid = NULL; |
| PKIX_Boolean hasFreshStatus = PKIX_FALSE; |
| PKIX_Boolean statusIsGood = PKIX_FALSE; |
| SECErrorCodes resultCode = SEC_ERROR_REVOKED_CERTIFICATE_OCSP; |
| PKIX_RevocationStatus revStatus = PKIX_RevStatus_NoInfo; |
| |
| PKIX_ENTER(OCSPCHECKER, "pkix_OcspChecker_CheckLocal"); |
| |
| PKIX_CHECK( |
| PKIX_PL_OcspCertID_Create(cert, NULL, &cid, |
| plContext), |
| PKIX_OCSPCERTIDCREATEFAILED); |
| if (!cid) { |
| goto cleanup; |
| } |
| |
| PKIX_CHECK( |
| PKIX_PL_OcspCertID_GetFreshCacheStatus(cid, date, |
| &hasFreshStatus, |
| &statusIsGood, |
| &resultCode, |
| plContext), |
| PKIX_OCSPCERTIDGETFRESHCACHESTATUSFAILED); |
| if (hasFreshStatus) { |
| if (statusIsGood) { |
| revStatus = PKIX_RevStatus_Success; |
| resultCode = 0; |
| } else { |
| revStatus = pkix_OcspChecker_MapResultCodeToRevStatus(resultCode); |
| } |
| } |
| |
| cleanup: |
| *pRevStatus = revStatus; |
| |
| /* ocsp carries only tree statuses: good, bad, and unknown. |
| * revStatus is used to pass them. reasonCode is always set |
| * to be unknown. */ |
| *pReasonCode = crlEntryReasonUnspecified; |
| PKIX_DECREF(cid); |
| |
| PKIX_RETURN(OCSPCHECKER); |
| } |
| |
| |
| /* |
| * The OCSPChecker is created in an idle state, and remains in this state until |
| * either (a) the default Responder has been set and enabled, and a Check |
| * request is received with no responder specified, or (b) a Check request is |
| * received with a specified responder. A request message is constructed and |
| * given to the HttpClient. When a response is received it is decoded and the |
| * results provided to the caller. |
| * |
| * During the most recent enhancement of this function, it has been found that |
| * it doesn't correctly implement non-blocking I/O. |
| * |
| * The nbioContext is used in two places, for "response-obtaining" and |
| * for "response-verification". |
| * |
| * However, if this function gets called to resume, it always |
| * repeats the "request creation" and "response fetching" steps! |
| * As a result, the earlier operation is never resumed. |
| */ |
| PKIX_Error * |
| pkix_OcspChecker_CheckExternal( |
| PKIX_PL_Cert *cert, |
| PKIX_PL_Cert *issuer, |
| PKIX_PL_Date *date, |
| pkix_RevocationMethod *checkerObject, |
| PKIX_ProcessingParams *procParams, |
| PKIX_UInt32 methodFlags, |
| PKIX_RevocationStatus *pRevStatus, |
| CERTCRLEntryReasonCode *pReasonCode, |
| void **pNBIOContext, |
| void *plContext) |
| { |
| SECErrorCodes resultCode = SEC_ERROR_REVOKED_CERTIFICATE_OCSP; |
| PKIX_Boolean uriFound = PKIX_FALSE; |
| PKIX_Boolean passed = PKIX_TRUE; |
| pkix_OcspChecker *checker = NULL; |
| PKIX_PL_OcspCertID *cid = NULL; |
| PKIX_PL_OcspRequest *request = NULL; |
| PKIX_PL_OcspResponse *response = NULL; |
| PKIX_PL_Date *validity = NULL; |
| PKIX_RevocationStatus revStatus = PKIX_RevStatus_NoInfo; |
| void *nbioContext = NULL; |
| enum { stageGET, stagePOST } currentStage; |
| PRBool retry = PR_FALSE; |
| |
| PKIX_ENTER(OCSPCHECKER, "pkix_OcspChecker_CheckExternal"); |
| |
| PKIX_CHECK( |
| pkix_CheckType((PKIX_PL_Object*)checkerObject, |
| PKIX_OCSPCHECKER_TYPE, plContext), |
| PKIX_OBJECTNOTOCSPCHECKER); |
| |
| checker = (pkix_OcspChecker *)checkerObject; |
| |
| PKIX_CHECK( |
| PKIX_PL_OcspCertID_Create(cert, NULL, &cid, |
| plContext), |
| PKIX_OCSPCERTIDCREATEFAILED); |
| |
| /* create request */ |
| PKIX_CHECK( |
| pkix_pl_OcspRequest_Create(cert, cid, validity, NULL, |
| methodFlags, &uriFound, &request, |
| plContext), |
| PKIX_OCSPREQUESTCREATEFAILED); |
| |
| if (uriFound == PKIX_FALSE) { |
| /* no caching for certs lacking URI */ |
| resultCode = 0; |
| goto cleanup; |
| } |
| |
| if (methodFlags & CERT_REV_M_FORCE_POST_METHOD_FOR_OCSP) { |
| /* Do not try HTTP GET, only HTTP POST */ |
| currentStage = stagePOST; |
| } else { |
| /* Try HTTP GET first, falling back to POST */ |
| currentStage = stageGET; |
| } |
| |
| do { |
| const char *method; |
| passed = PKIX_TRUE; |
| |
| retry = PR_FALSE; |
| if (currentStage == stageGET) { |
| method = "GET"; |
| } else { |
| PORT_Assert(currentStage == stagePOST); |
| method = "POST"; |
| } |
| |
| /* send request and create a response object */ |
| PKIX_CHECK_NO_GOTO( |
| pkix_pl_OcspResponse_Create(request, method, NULL, |
| checker->certVerifyFcn, |
| &nbioContext, |
| &response, |
| plContext), |
| PKIX_OCSPRESPONSECREATEFAILED); |
| |
| if (pkixErrorResult) { |
| passed = PKIX_FALSE; |
| } |
| |
| if (passed && nbioContext != 0) { |
| *pNBIOContext = nbioContext; |
| goto cleanup; |
| } |
| |
| if (passed){ |
| PKIX_CHECK_NO_GOTO( |
| pkix_pl_OcspResponse_Decode(response, &passed, |
| &resultCode, plContext), |
| PKIX_OCSPRESPONSEDECODEFAILED); |
| if (pkixErrorResult) { |
| passed = PKIX_FALSE; |
| } |
| } |
| |
| if (passed){ |
| PKIX_CHECK_NO_GOTO( |
| pkix_pl_OcspResponse_GetStatus(response, &passed, |
| &resultCode, plContext), |
| PKIX_OCSPRESPONSEGETSTATUSRETURNEDANERROR); |
| if (pkixErrorResult) { |
| passed = PKIX_FALSE; |
| } |
| } |
| |
| if (passed){ |
| PKIX_CHECK_NO_GOTO( |
| pkix_pl_OcspResponse_VerifySignature(response, cert, |
| procParams, &passed, |
| &nbioContext, plContext), |
| PKIX_OCSPRESPONSEVERIFYSIGNATUREFAILED); |
| if (pkixErrorResult) { |
| passed = PKIX_FALSE; |
| } else { |
| if (nbioContext != 0) { |
| *pNBIOContext = nbioContext; |
| goto cleanup; |
| } |
| } |
| } |
| |
| if (!passed && currentStage == stagePOST) { |
| /* We won't retry a POST failure, so it's final. |
| * Because the following block with its call to |
| * pkix_pl_OcspResponse_GetStatusForCert |
| * will take care of caching good or bad state, |
| * but we only execute that next block if there hasn't |
| * been a failure yet, we must cache the POST |
| * failure now. |
| */ |
| |
| if (cid && cid->certID) { |
| /* Caching MIGHT consume the cid. */ |
| PKIX_Error *err; |
| err = PKIX_PL_OcspCertID_RememberOCSPProcessingFailure( |
| cid, plContext); |
| if (err) { |
| PKIX_PL_Object_DecRef((PKIX_PL_Object*)err, plContext); |
| } |
| } |
| } |
| |
| if (passed){ |
| PKIX_Boolean allowCachingOfFailures = |
| (currentStage == stagePOST) ? PKIX_TRUE : PKIX_FALSE; |
| |
| PKIX_CHECK_NO_GOTO( |
| pkix_pl_OcspResponse_GetStatusForCert(cid, response, |
| allowCachingOfFailures, |
| date, |
| &passed, &resultCode, |
| plContext), |
| PKIX_OCSPRESPONSEGETSTATUSFORCERTFAILED); |
| if (pkixErrorResult) { |
| passed = PKIX_FALSE; |
| } else if (passed == PKIX_FALSE) { |
| revStatus = pkix_OcspChecker_MapResultCodeToRevStatus(resultCode); |
| } else { |
| revStatus = PKIX_RevStatus_Success; |
| } |
| } |
| |
| if (currentStage == stageGET && revStatus != PKIX_RevStatus_Success && |
| revStatus != PKIX_RevStatus_Revoked) { |
| /* we'll retry */ |
| PKIX_DECREF(response); |
| retry = PR_TRUE; |
| currentStage = stagePOST; |
| revStatus = PKIX_RevStatus_NoInfo; |
| if (pkixErrorResult) { |
| PKIX_PL_Object_DecRef((PKIX_PL_Object*)pkixErrorResult, |
| plContext); |
| pkixErrorResult = NULL; |
| } |
| } |
| } while (retry); |
| |
| cleanup: |
| if (revStatus == PKIX_RevStatus_NoInfo && (uriFound || |
| methodFlags & PKIX_REV_M_REQUIRE_INFO_ON_MISSING_SOURCE) && |
| methodFlags & PKIX_REV_M_FAIL_ON_MISSING_FRESH_INFO) { |
| revStatus = PKIX_RevStatus_Revoked; |
| } |
| *pRevStatus = revStatus; |
| |
| /* ocsp carries only three statuses: good, bad, and unknown. |
| * revStatus is used to pass them. reasonCode is always set |
| * to be unknown. */ |
| *pReasonCode = crlEntryReasonUnspecified; |
| |
| PKIX_DECREF(cid); |
| PKIX_DECREF(request); |
| PKIX_DECREF(response); |
| |
| PKIX_RETURN(OCSPCHECKER); |
| } |
| |
| |