// Extracts whatever information we need out of fd (using SSL_*) and passes it
// to SSLServerCertVerificationJob::Dispatch. SSLServerCertVerificationJob should
// never do anything with fd except logging.
SECStatus
AuthCertificateHook(void* arg, PRFileDesc* fd, PRBool checkSig, PRBool isServer)
{
  RefPtr<SharedCertVerifier> certVerifier(GetDefaultCertVerifier());
  if (!certVerifier) {
    PR_SetError(SEC_ERROR_NOT_INITIALIZED, 0);
    return SECFailure;
  }

  // Runs on the socket transport thread

  PR_LOG(gPIPNSSLog, PR_LOG_DEBUG,
         ("[%p] starting AuthCertificateHook\n", fd));

  // Modern libssl always passes PR_TRUE for checkSig, and we have no means of
  // doing verification without checking signatures.
  NS_ASSERTION(checkSig, "AuthCertificateHook: checkSig unexpectedly false");

  // PSM never causes libssl to call this function with PR_TRUE for isServer,
  // and many things in PSM assume that we are a client.
  NS_ASSERTION(!isServer, "AuthCertificateHook: isServer unexpectedly true");

  nsNSSSocketInfo* socketInfo = static_cast<nsNSSSocketInfo*>(arg);

  ScopedCERTCertificate serverCert(SSL_PeerCertificate(fd));

  if (!checkSig || isServer || !socketInfo || !serverCert) {
      PR_SetError(PR_INVALID_STATE_ERROR, 0);
      return SECFailure;
  }

  socketInfo->SetFullHandshake();

  // This value of "now" is used both here for OCSP stapling and later
  // when calling CreateCertErrorRunnable.
  PRTime now = PR_Now();

  if (BlockServerCertChangeForSpdy(socketInfo, serverCert) != SECSuccess)
    return SECFailure;

  bool onSTSThread;
  nsresult nrv;
  nsCOMPtr<nsIEventTarget> sts
    = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &nrv);
  if (NS_SUCCEEDED(nrv)) {
    nrv = sts->IsOnCurrentThread(&onSTSThread);
  }

  if (NS_FAILED(nrv)) {
    NS_ERROR("Could not get STS service or IsOnCurrentThread failed");
    PR_SetError(PR_UNKNOWN_ERROR, 0);
    return SECFailure;
  }

  // SSL_PeerStapledOCSPResponses will never return a non-empty response if
  // OCSP stapling wasn't enabled because libssl wouldn't have let the server
  // return a stapled OCSP response.
  // We don't own these pointers.
  const SECItemArray* csa = SSL_PeerStapledOCSPResponses(fd);
  SECItem* stapledOCSPResponse = nullptr;
  // we currently only support single stapled responses
  if (csa && csa->len == 1) {
    stapledOCSPResponse = &csa->items[0];
  }

  uint32_t providerFlags = 0;
  socketInfo->GetProviderFlags(&providerFlags);

  if (onSTSThread) {

    // We *must* do certificate verification on a background thread because
    // we need the socket transport thread to be free for our OCSP requests,
    // and we *want* to do certificate verification on a background thread
    // because of the performance benefits of doing so.
    socketInfo->SetCertVerificationWaiting();
    SECStatus rv = SSLServerCertVerificationJob::Dispatch(
                     certVerifier, static_cast<const void*>(fd), socketInfo,
                     serverCert, stapledOCSPResponse, providerFlags, now);
    return rv;
  }

  // We can't do certificate verification on a background thread, because the
  // thread doing the network I/O may not interrupt its network I/O on receipt
  // of our SSLServerCertVerificationResult event, and/or it might not even be
  // a non-blocking socket.

  SECStatus rv = AuthCertificate(*certVerifier, socketInfo, serverCert,
                                 stapledOCSPResponse, providerFlags, now);
  if (rv == SECSuccess) {
    Telemetry::Accumulate(Telemetry::SSL_CERT_ERROR_OVERRIDES, 1);
    return SECSuccess;
  }

  PRErrorCode error = PR_GetError();
  if (error != 0) {
    RefPtr<CertErrorRunnable> runnable(
        CreateCertErrorRunnable(*certVerifier, error, socketInfo, serverCert,
                                static_cast<const void*>(fd), providerFlags,
                                now));
    if (!runnable) {
      // CreateCertErrorRunnable sets a new error code when it fails
      error = PR_GetError();
    } else {
      // We have to return SECSuccess or SECFailure based on the result of the
      // override processing, so we must block this thread waiting for it. The
      // CertErrorRunnable will NOT dispatch the result at all, since we passed
      // false for CreateCertErrorRunnable's async parameter
      nrv = runnable->DispatchToMainThreadAndWait();
      if (NS_FAILED(nrv)) {
        NS_ERROR("Failed to dispatch CertErrorRunnable");
        PR_SetError(PR_INVALID_STATE_ERROR, 0);
        return SECFailure;
      }

      if (!runnable->mResult) {
        NS_ERROR("CertErrorRunnable did not set result");
        PR_SetError(PR_INVALID_STATE_ERROR, 0);
        return SECFailure;
      }

      if (runnable->mResult->mErrorCode == 0) {
        return SECSuccess; // cert error override occurred.
      }

      // We must call SetCanceled here to set the error message type
      // in case it isn't PlainErrorMessage, which is what we would
      // default to if we just called
      // PR_SetError(runnable->mResult->mErrorCode, 0) and returned
      // SECFailure without doing this.
      socketInfo->SetCanceled(runnable->mResult->mErrorCode,
                              runnable->mResult->mErrorMessageType);
      error = runnable->mResult->mErrorCode;
    }
  }

  if (error == 0) {
    NS_ERROR("error code not set");
    error = PR_UNKNOWN_ERROR;
  }

  PR_SetError(error, 0);
  return SECFailure;
}
// Extracts whatever information we need out of fd (using SSL_*) and passes it
// to SSLServerCertVerificationJob::Dispatch. SSLServerCertVerificationJob should
// never do anything with fd except logging.
SECStatus
AuthCertificateHook(void *arg, PRFileDesc *fd, PRBool checkSig, PRBool isServer)
{
    // Runs on the socket transport thread

    PR_LOG(gPIPNSSLog, PR_LOG_DEBUG,
           ("[%p] starting AuthCertificateHook\n", fd));

    // Modern libssl always passes PR_TRUE for checkSig, and we have no means of
    // doing verification without checking signatures.
    NS_ASSERTION(checkSig, "AuthCertificateHook: checkSig unexpectedly false");

    // PSM never causes libssl to call this function with PR_TRUE for isServer,
    // and many things in PSM assume that we are a client.
    NS_ASSERTION(!isServer, "AuthCertificateHook: isServer unexpectedly true");

    nsNSSSocketInfo *socketInfo = static_cast<nsNSSSocketInfo*>(arg);

    if (socketInfo) {
        // This is the first callback during full handshakes.
        socketInfo->SetFirstServerHelloReceived();
    }

    CERTCertificate *serverCert = SSL_PeerCertificate(fd);
    CERTCertificateCleaner serverCertCleaner(serverCert);

    if (!checkSig || isServer || !socketInfo || !serverCert) {
        PR_SetError(PR_INVALID_STATE_ERROR, 0);
        return SECFailure;
    }

    if (BlockServerCertChangeForSpdy(socketInfo, serverCert) != SECSuccess)
        return SECFailure;

    bool onSTSThread;
    nsresult nrv;
    nsCOMPtr<nsIEventTarget> sts
        = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &nrv);
    if (NS_SUCCEEDED(nrv)) {
        nrv = sts->IsOnCurrentThread(&onSTSThread);
    }

    if (NS_FAILED(nrv)) {
        NS_ERROR("Could not get STS service or IsOnCurrentThread failed");
        PR_SetError(PR_UNKNOWN_ERROR, 0);
        return SECFailure;
    }

    if (onSTSThread) {
        // We *must* do certificate verification on a background thread because
        // we need the socket transport thread to be free for our OCSP requests,
        // and we *want* to do certificate verification on a background thread
        // because of the performance benefits of doing so.
        socketInfo->SetCertVerificationWaiting();
        SECStatus rv = SSLServerCertVerificationJob::Dispatch(
                           static_cast<const void *>(fd), socketInfo, serverCert);
        return rv;
    }

    // We can't do certificate verification on a background thread, because the
    // thread doing the network I/O may not interrupt its network I/O on receipt
    // of our SSLServerCertVerificationResult event, and/or it might not even be
    // a non-blocking socket.
    SECStatus rv = AuthCertificate(socketInfo, serverCert);
    if (rv == SECSuccess) {
        return SECSuccess;
    }

    PRErrorCode error = PR_GetError();
    if (error != 0) {
        nsRefPtr<CertErrorRunnable> runnable = CreateCertErrorRunnable(
                error, socketInfo, serverCert,
                static_cast<const void *>(fd));
        if (!runnable) {
            // CreateCertErrorRunnable sets a new error code when it fails
            error = PR_GetError();
        } else {
            // We have to return SECSuccess or SECFailure based on the result of the
            // override processing, so we must block this thread waiting for it. The
            // CertErrorRunnable will NOT dispatch the result at all, since we passed
            // false for CreateCertErrorRunnable's async parameter
            nrv = runnable->DispatchToMainThreadAndWait();
            if (NS_FAILED(nrv)) {
                NS_ERROR("Failed to dispatch CertErrorRunnable");
                PR_SetError(PR_INVALID_STATE_ERROR, 0);
                return SECFailure;
            }

            if (!runnable->mResult) {
                NS_ERROR("CertErrorRunnable did not set result");
                PR_SetError(PR_INVALID_STATE_ERROR, 0);
                return SECFailure;
            }

            if (runnable->mResult->mErrorCode == 0) {
                return SECSuccess; // cert error override occurred.
            }

            // We must call SetCanceled here to set the error message type
            // in case it isn't PlainErrorMessage, which is what we would
            // default to if we just called
            // PR_SetError(runnable->mResult->mErrorCode, 0) and returned
            // SECFailure without doing this.
            socketInfo->SetCanceled(runnable->mResult->mErrorCode,
                                    runnable->mResult->mErrorMessageType);
            error = runnable->mResult->mErrorCode;
        }
    }

    if (error == 0) {
        NS_ERROR("error code not set");
        error = PR_UNKNOWN_ERROR;
    }

    PR_SetError(error, 0);
    return SECFailure;
}