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