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: