SECStatus
NSSCertDBTrustDomain::CheckRevocation(
  insanity::pkix::EndEntityOrCA endEntityOrCA,
  const CERTCertificate* cert,
  /*const*/ CERTCertificate* issuerCert,
  PRTime time,
  /*optional*/ const SECItem* stapledOCSPResponse)
{
  // Actively distrusted certificates will have already been blocked by
  // GetCertTrust.

  // TODO: need to verify that IsRevoked isn't called for trust anchors AND
  // that that fact is documented in insanity.

  PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
         ("NSSCertDBTrustDomain: Top of CheckRevocation\n"));

  PORT_Assert(cert);
  PORT_Assert(issuerCert);
  if (!cert || !issuerCert) {
    PORT_SetError(SEC_ERROR_INVALID_ARGS);
    return SECFailure;
  }

  // If we have a stapled OCSP response then the verification of that response
  // determines the result unless the OCSP response is expired. We make an
  // exception for expired responses because some servers, nginx in particular,
  // are known to serve expired responses due to bugs.
  if (stapledOCSPResponse) {
    PR_ASSERT(endEntityOrCA == MustBeEndEntity);
    SECStatus rv = VerifyEncodedOCSPResponse(*this, cert, issuerCert, time,
                                             stapledOCSPResponse);
    if (rv == SECSuccess) {
      return rv;
    }
    if (PR_GetError() != SEC_ERROR_OCSP_OLD_RESPONSE) {
      return rv;
    }
  }

  // TODO(bug 921885): We need to change this when we add EV support.

  // TODO: when !mOCSPDownloadEnabled, we still need to handle the fallback for
  // expired responses. But, if/when we disable OCSP fetching by default, it
  // would be ambiguous whether !mOCSPDownloadEnabled means "I want the default"
  // or "I really never want you to ever fetch OCSP."
  if (mOCSPDownloadEnabled) {
    // We don't do OCSP fetching for intermediates.
    if (endEntityOrCA == MustBeCA) {
      PR_ASSERT(!stapledOCSPResponse);
      return SECSuccess;
    }

    ScopedPtr<char, PORT_Free_string>
      url(CERT_GetOCSPAuthorityInfoAccessLocation(cert));

    // Nothing to do if we don't have an OCSP responder URI for the cert; just
    // assume it is good. Note that this is the confusing, but intended,
    // interpretation of "strict" revocation checking in the face of a
    // certificate that lacks an OCSP responder URI.
    if (!url) {
      if (stapledOCSPResponse) {
        PR_SetError(SEC_ERROR_OCSP_OLD_RESPONSE, 0);
        return SECFailure;
      }
      return SECSuccess;
    }

    ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
    if (!arena) {
      return SECFailure;
    }

    const SECItem* request
      = CreateEncodedOCSPRequest(arena.get(), cert, issuerCert);
    if (!request) {
      return SECFailure;
    }

    const SECItem* response(CERT_PostOCSPRequest(arena.get(), url.get(),
                                                 request));
    if (!response) {
      if (mOCSPStrict) {
        return SECFailure;
      }
      // Soft fail -> success :(
    } else {
      SECStatus rv = VerifyEncodedOCSPResponse(*this, cert, issuerCert, time,
                                               response);
      if (rv == SECSuccess) {
        return SECSuccess;
      }
      PRErrorCode error = PR_GetError();
      switch (error) {
        case SEC_ERROR_OCSP_UNKNOWN_CERT:
        case SEC_ERROR_REVOKED_CERTIFICATE:
          return SECFailure;
        default:
          if (mOCSPStrict) {
            return SECFailure;
          }
          break; // Soft fail -> success :(
      }
    }
  }

  PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
         ("NSSCertDBTrustDomain: end of CheckRevocation"));

  return SECSuccess;
}
SECStatus
NSSCertDBTrustDomain::CheckRevocation(
  mozilla::pkix::EndEntityOrCA endEntityOrCA,
  const CERTCertificate* cert,
  /*const*/ CERTCertificate* issuerCert,
  PRTime time,
  /*optional*/ const SECItem* stapledOCSPResponse)
{
  // Actively distrusted certificates will have already been blocked by
  // GetCertTrust.

  // TODO: need to verify that IsRevoked isn't called for trust anchors AND
  // that that fact is documented in mozillapkix.

  PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
         ("NSSCertDBTrustDomain: Top of CheckRevocation\n"));

  PORT_Assert(cert);
  PORT_Assert(issuerCert);
  if (!cert || !issuerCert) {
    PORT_SetError(SEC_ERROR_INVALID_ARGS);
    return SECFailure;
  }

  // Bug 991815: The BR allow OCSP for intermediates to be up to one year old.
  // Since this affects EV there is no reason why DV should be more strict
  // so all intermediatates are allowed to have OCSP responses up to one year
  // old.
  uint16_t maxOCSPLifetimeInDays = 10;
  if (endEntityOrCA == EndEntityOrCA::MustBeCA) {
    maxOCSPLifetimeInDays = 365;
  }

  // If we have a stapled OCSP response then the verification of that response
  // determines the result unless the OCSP response is expired. We make an
  // exception for expired responses because some servers, nginx in particular,
  // are known to serve expired responses due to bugs.
  // We keep track of the result of verifying the stapled response but don't
  // immediately return failure if the response has expired.
  PRErrorCode stapledOCSPResponseErrorCode = 0;
  if (stapledOCSPResponse) {
    PR_ASSERT(endEntityOrCA == MustBeEndEntity);
    bool expired;
    SECStatus rv = VerifyAndMaybeCacheEncodedOCSPResponse(cert, issuerCert,
                                                          time,
                                                          maxOCSPLifetimeInDays,
                                                          stapledOCSPResponse,
                                                          ResponseWasStapled,
                                                          expired);
    if (rv == SECSuccess) {
      // stapled OCSP response present and good
      Telemetry::Accumulate(Telemetry::SSL_OCSP_STAPLING, 1);
      PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
             ("NSSCertDBTrustDomain: stapled OCSP response: good"));
      return rv;
    }
    stapledOCSPResponseErrorCode = PR_GetError();
    if (stapledOCSPResponseErrorCode == SEC_ERROR_OCSP_OLD_RESPONSE ||
        expired) {
      // stapled OCSP response present but expired
      Telemetry::Accumulate(Telemetry::SSL_OCSP_STAPLING, 3);
      PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
             ("NSSCertDBTrustDomain: expired stapled OCSP response"));
    } else {
      // stapled OCSP response present but invalid for some reason
      Telemetry::Accumulate(Telemetry::SSL_OCSP_STAPLING, 4);
      PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
             ("NSSCertDBTrustDomain: stapled OCSP response: failure"));
      return rv;
    }
  } else {
    // no stapled OCSP response
    Telemetry::Accumulate(Telemetry::SSL_OCSP_STAPLING, 2);
    PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
           ("NSSCertDBTrustDomain: no stapled OCSP response"));
  }

  PRErrorCode cachedResponseErrorCode = 0;
  PRTime cachedResponseValidThrough = 0;
  bool cachedResponsePresent = mOCSPCache.Get(cert, issuerCert,
                                              cachedResponseErrorCode,
                                              cachedResponseValidThrough);
  if (cachedResponsePresent) {
    if (cachedResponseErrorCode == 0 && cachedResponseValidThrough >= time) {
      PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
             ("NSSCertDBTrustDomain: cached OCSP response: good"));
      return SECSuccess;
    }
    // If we have a cached revoked response, use it.
    if (cachedResponseErrorCode == SEC_ERROR_REVOKED_CERTIFICATE) {
      PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
             ("NSSCertDBTrustDomain: cached OCSP response: revoked"));
      PR_SetError(SEC_ERROR_REVOKED_CERTIFICATE, 0);
      return SECFailure;
    }
    // The cached response may indicate an unknown certificate or it may be
    // expired. Don't return with either of these statuses yet - we may be
    // able to fetch a more recent one.
    PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
           ("NSSCertDBTrustDomain: cached OCSP response: error %ld valid "
           "until %lld", cachedResponseErrorCode, cachedResponseValidThrough));
    // When a good cached response has expired, it is more convenient
    // to convert that to an error code and just deal with
    // cachedResponseErrorCode from here on out.
    if (cachedResponseErrorCode == 0 && cachedResponseValidThrough < time) {
      cachedResponseErrorCode = SEC_ERROR_OCSP_OLD_RESPONSE;
    }
    // We may have a cached indication of server failure. Ignore it if
    // it has expired.
    if (cachedResponseErrorCode != 0 &&
        cachedResponseErrorCode != SEC_ERROR_OCSP_UNKNOWN_CERT &&
        cachedResponseErrorCode != SEC_ERROR_OCSP_OLD_RESPONSE &&
        cachedResponseValidThrough < time) {
      cachedResponseErrorCode = 0;
      cachedResponsePresent = false;
    }
  } else {
    PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
           ("NSSCertDBTrustDomain: no cached OCSP response"));
  }
  // At this point, if and only if cachedErrorResponseCode is 0, there was no
  // cached response.
  PR_ASSERT((!cachedResponsePresent && cachedResponseErrorCode == 0) ||
            (cachedResponsePresent && cachedResponseErrorCode != 0));

  // TODO: We still need to handle the fallback for expired responses. But,
  // if/when we disable OCSP fetching by default, it would be ambiguous whether
  // security.OCSP.enable==0 means "I want the default" or "I really never want
  // you to ever fetch OCSP."

  if ((mOCSPFetching == NeverFetchOCSP) ||
      (endEntityOrCA == MustBeCA && (mOCSPFetching == FetchOCSPForDVHardFail ||
                                     mOCSPFetching == FetchOCSPForDVSoftFail))) {
    // We're not going to be doing any fetching, so if there was a cached
    // "unknown" response, say so.
    if (cachedResponseErrorCode == SEC_ERROR_OCSP_UNKNOWN_CERT) {
      PR_SetError(SEC_ERROR_OCSP_UNKNOWN_CERT, 0);
      return SECFailure;
    }
    // If we're doing hard-fail, we want to know if we have a cached response
    // that has expired.
    if (mOCSPFetching == FetchOCSPForDVHardFail &&
        cachedResponseErrorCode == SEC_ERROR_OCSP_OLD_RESPONSE) {
      PR_SetError(SEC_ERROR_OCSP_OLD_RESPONSE, 0);
      return SECFailure;
    }

    return SECSuccess;
  }

  if (mOCSPFetching == LocalOnlyOCSPForEV) {
    PR_SetError(cachedResponseErrorCode != 0 ? cachedResponseErrorCode
                                             : SEC_ERROR_OCSP_UNKNOWN_CERT, 0);
    return SECFailure;
  }

  ScopedPtr<char, PORT_Free_string>
    url(CERT_GetOCSPAuthorityInfoAccessLocation(cert));

  if (!url) {
    if (mOCSPFetching == FetchOCSPForEV ||
        cachedResponseErrorCode == SEC_ERROR_OCSP_UNKNOWN_CERT) {
      PR_SetError(SEC_ERROR_OCSP_UNKNOWN_CERT, 0);
      return SECFailure;
    }
    if (cachedResponseErrorCode == SEC_ERROR_OCSP_OLD_RESPONSE) {
      PR_SetError(SEC_ERROR_OCSP_OLD_RESPONSE, 0);
      return SECFailure;
    }
    if (stapledOCSPResponseErrorCode != 0) {
      PR_SetError(stapledOCSPResponseErrorCode, 0);
      return SECFailure;
    }

    // Nothing to do if we don't have an OCSP responder URI for the cert; just
    // assume it is good. Note that this is the confusing, but intended,
    // interpretation of "strict" revocation checking in the face of a
    // certificate that lacks an OCSP responder URI.
    return SECSuccess;
  }

  ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
  if (!arena) {
    return SECFailure;
  }

  // Only request a response if we didn't have a cached indication of failure
  // (don't keep requesting responses from a failing server).
  const SECItem* response = nullptr;
  if (cachedResponseErrorCode == 0 ||
      cachedResponseErrorCode == SEC_ERROR_OCSP_UNKNOWN_CERT ||
      cachedResponseErrorCode == SEC_ERROR_OCSP_OLD_RESPONSE) {
    const SECItem* request(CreateEncodedOCSPRequest(arena.get(), cert,
                                                    issuerCert));
    if (!request) {
      return SECFailure;
    }

    response = DoOCSPRequest(arena.get(), url.get(), request,
                             OCSPFetchingTypeToTimeoutTime(mOCSPFetching));
  }

  if (!response) {
    PRErrorCode error = PR_GetError();
    if (error == 0) {
      error = cachedResponseErrorCode;
    }
    PRTime timeout = time + ServerFailureDelay;
    if (mOCSPCache.Put(cert, issuerCert, error, time, timeout) != SECSuccess) {
      return SECFailure;
    }
    PR_SetError(error, 0);
    if (mOCSPFetching != FetchOCSPForDVSoftFail) {
      PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
             ("NSSCertDBTrustDomain: returning SECFailure after "
              "OCSP request failure"));
      return SECFailure;
    }
    if (cachedResponseErrorCode == SEC_ERROR_OCSP_UNKNOWN_CERT) {
      PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
             ("NSSCertDBTrustDomain: returning SECFailure from cached "
              "response after OCSP request failure"));
      PR_SetError(cachedResponseErrorCode, 0);
      return SECFailure;
    }
    if (stapledOCSPResponseErrorCode != 0) {
      PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
             ("NSSCertDBTrustDomain: returning SECFailure from expired "
              "stapled response after OCSP request failure"));
      PR_SetError(stapledOCSPResponseErrorCode, 0);
      return SECFailure;
    }

    PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
           ("NSSCertDBTrustDomain: returning SECSuccess after "
            "OCSP request failure"));
    return SECSuccess; // Soft fail -> success :(
  }

  // If the response from the network has expired but indicates a revoked
  // or unknown certificate, PR_GetError() will return the appropriate error.
  // We actually ignore expired here.
  bool expired;
  SECStatus rv = VerifyAndMaybeCacheEncodedOCSPResponse(cert, issuerCert, time,
                                                        maxOCSPLifetimeInDays,
                                                        response,
                                                        ResponseIsFromNetwork,
                                                        expired);
  if (rv == SECSuccess || mOCSPFetching != FetchOCSPForDVSoftFail) {
    PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
      ("NSSCertDBTrustDomain: returning after VerifyEncodedOCSPResponse"));
    return rv;
  }

  PRErrorCode error = PR_GetError();
  if (error == SEC_ERROR_OCSP_UNKNOWN_CERT ||
      error == SEC_ERROR_REVOKED_CERTIFICATE) {
    return rv;
  }

  if (stapledOCSPResponseErrorCode != 0) {
    PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
           ("NSSCertDBTrustDomain: returning SECFailure from expired stapled "
            "response after OCSP request verification failure"));
    PR_SetError(stapledOCSPResponseErrorCode, 0);
    return SECFailure;
  }

  PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
         ("NSSCertDBTrustDomain: end of CheckRevocation"));

  return SECSuccess; // Soft fail -> success :(
}