SECStatus
NSSCertDBTrustDomain::VerifyAndMaybeCacheEncodedOCSPResponse(
  const CERTCertificate* cert, CERTCertificate* issuerCert, PRTime time,
  uint16_t maxLifetimeInDays, const SECItem* encodedResponse,
  EncodedResponseSource responseSource, /*out*/ bool& expired)
{
  PRTime thisUpdate = 0;
  PRTime validThrough = 0;
  SECStatus rv = VerifyEncodedOCSPResponse(*this, cert, issuerCert, time,
                                           maxLifetimeInDays, encodedResponse,
                                           expired, &thisUpdate, &validThrough);
  PRErrorCode error = (rv == SECSuccess ? 0 : PR_GetError());
  // If a response was stapled and expired, we don't want to cache it. Return
  // early to simplify the logic here.
  if (responseSource == ResponseWasStapled && expired) {
    PR_ASSERT(rv != SECSuccess);
    return rv;
  }
  // validThrough is only trustworthy if the response successfully verifies
  // or it indicates a revoked or unknown certificate.
  // If this isn't the case, store an indication of failure (to prevent
  // repeatedly requesting a response from a failing server).
  if (rv != SECSuccess && error != SEC_ERROR_REVOKED_CERTIFICATE &&
      error != SEC_ERROR_OCSP_UNKNOWN_CERT) {
    validThrough = time + ServerFailureDelay;
  }
  if (responseSource == ResponseIsFromNetwork ||
      rv == SECSuccess ||
      error == SEC_ERROR_REVOKED_CERTIFICATE ||
      error == SEC_ERROR_OCSP_UNKNOWN_CERT) {
    PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
           ("NSSCertDBTrustDomain: caching OCSP response"));
    if (mOCSPCache.Put(cert, issuerCert, error, thisUpdate, validThrough)
          != SECSuccess) {
      return SECFailure;
    }
  }

  // If the verification failed, re-set to that original error
  // (the call to Put may have un-set it).
  if (rv != SECSuccess) {
    PR_SetError(error, 0);
  }
  return rv;
}
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;
}