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