/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// During certificate authentication, we call CertVerifier::VerifySSLServerCert.
// This function may make zero or more HTTP requests (e.g. to gather revocation
// information). Our fetching logic for these requests processes them on the
// socket transport service thread.
//
// Because the connection for which we are verifying the certificate is
// happening on the socket transport thread, if our cert auth hook were to call
// VerifySSLServerCert directly, there would be a deadlock: VerifySSLServerCert
// would cause an event to be asynchronously posted to the socket transport
// thread, and then it would block the socket transport thread waiting to be
// notified of the HTTP response. However, the HTTP request would never actually
// be processed because the socket transport thread would be blocked and so it
// wouldn't be able process HTTP requests.
//
// Consequently, when we are asked to verify a certificate, we must always call
// VerifySSLServerCert on another thread. To accomplish this, our auth cert hook
// dispatches a SSLServerCertVerificationJob to a pool of background threads,
// and then immediately returns SECWouldBlock to libssl. These jobs are where
// VerifySSLServerCert is actually called.
//
// When our auth cert hook returns SECWouldBlock, libssl will carry on the
// handshake while we validate the certificate. This will free up the socket
// transport thread so that HTTP requests--including the OCSP requests needed
// for cert verification as mentioned above--can be processed.
//
// Once VerifySSLServerCert returns, the cert verification job dispatches a
// SSLServerCertVerificationResult to the socket transport thread; the
// SSLServerCertVerificationResult will notify libssl that the certificate
// authentication is complete. Once libssl is notified that the authentication
// is complete, it will continue the TLS handshake (if it hasn't already
// finished) and it will begin allowing us to send/receive data on the
// connection.
//
// Timeline of events (for connections managed by the socket transport service):
//
//    * libssl calls SSLServerCertVerificationJob::Dispatch on the socket
//      transport thread.
//    * SSLServerCertVerificationJob::Dispatch queues a job
//      (instance of SSLServerCertVerificationJob) to its background thread
//      pool and returns.
//    * One of the background threads calls CertVerifier::VerifySSLServerCert,
//      which may enqueue some HTTP request(s) onto the socket transport thread,
//      and then blocks that background thread waiting for the responses and/or
//      timeouts or errors for those requests.
//    * Once those HTTP responses have all come back or failed, the
//      CertVerifier::VerifySSLServerCert function returns a result indicating
//      that the validation succeeded or failed.
//    * If the validation succeeded, then a SSLServerCertVerificationResult
//      event is posted to the socket transport thread, and the cert
//      verification thread becomes free to verify other certificates.
//    * Otherwise, we do cert override processing to see if the validation
//      error can be convered by override rules. The result of this processing
//      is similarly dispatched in a SSLServerCertVerificationResult.
//    * The SSLServerCertVerificationResult event will either wake up the
//      socket (using SSL_AuthCertificateComplete) if validation succeeded or
//      there was an error override, or it will set an error flag so that the
//      next I/O operation on the socket will fail, causing the socket transport
//      thread to close the connection.
//
// SSLServerCertVerificationResult must be dispatched to the socket transport
// thread because we must only call SSL_* functions on the socket transport
// thread since they may do I/O, because many parts of NSSSocketControl and the
// PSM NSS I/O layer are not thread-safe, and because we need the event to
// interrupt the PR_Poll that may waiting for I/O on the socket for which we
// are validating the cert.
//
// When socket process is enabled, libssl is running on socket process. To
// perform certificate authentication with CertVerifier, we have to send all
// needed information to parent process and send the result back to socket
// process via IPC. The workflow is described below.
// 1. In AuthCertificateHookInternal(), we call RemoteProcessCertVerification()
//    instead of SSLServerCertVerificationJob::Dispatch when we are on socket
//    process.
// 2. In RemoteProcessCertVerification(), PVerifySSLServerCert actors will be
//    created on IPDL background thread for carrying needed information via IPC.
// 3. On parent process, VerifySSLServerCertParent is created and it calls
//    SSLServerCertVerificationJob::Dispatch for doing certificate verification
//    on one of CertVerificationThreads.
// 4. When validation is done, OnVerifiedSSLServerCertSuccess IPC message is
//    sent through the IPDL background thread when
//    CertVerifier::VerifySSLServerCert returns Success. Otherwise,
//    OnVerifiedSSLServerCertFailure is sent.
// 5. After setp 4, PVerifySSLServerCert actors will be released. The
//    verification result will be dispatched via
//    SSLServerCertVerificationResult.

#include "SSLServerCertVerification.h"

#include <cstring>

#include "CertVerifier.h"
#include "CryptoTask.h"
#include "ExtendedValidation.h"
#include "NSSCertDBTrustDomain.h"
#include "NSSSocketControl.h"
#include "PSMRunnable.h"
#include "RootCertificateTelemetryUtils.h"
#include "ScopedNSSTypes.h"
#include "SharedCertVerifier.h"
#include "SharedSSLState.h"
#include "VerifySSLServerCertChild.h"
#include "cert.h"
#include "mozilla/Assertions.h"
#include "mozilla/Casting.h"
#include "mozilla/RefPtr.h"
#include "mozilla/Telemetry.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/Unused.h"
#include "nsComponentManagerUtils.h"
#include "nsContentUtils.h"
#include "nsICertOverrideService.h"
#include "nsIPublicKeyPinningService.h"
#include "nsISiteSecurityService.h"
#include "nsISocketProvider.h"
#include "nsThreadPool.h"
#include "nsNetUtil.h"
#include "nsNSSCertificate.h"
#include "nsNSSComponent.h"
#include "nsNSSIOLayer.h"
#include "nsServiceManagerUtils.h"
#include "nsString.h"
#include "nsURLHelper.h"
#include "nsXPCOMCIDInternal.h"
#include "mozpkix/pkix.h"
#include "mozpkix/pkixcheck.h"
#include "mozpkix/pkixnss.h"
#include "mozpkix/pkixutil.h"
#include "secerr.h"
#include "secport.h"
#include "ssl.h"
#include "sslerr.h"
#include "sslexp.h"

extern mozilla::LazyLogModule gPIPNSSLog;

using namespace mozilla::pkix;

namespace mozilla {
namespace psm {

// do not use a nsCOMPtr to avoid static initializer/destructor
nsIThreadPool* gCertVerificationThreadPool = nullptr;

// Called when the socket transport thread starts, to initialize the SSL cert
// verification thread pool. By tying the thread pool startup/shutdown directly
// to the STS thread's lifetime, we ensure that they are *always* available for
// SSL connections and that there are no races during startup and especially
// shutdown. (Previously, we have had multiple problems with races in PSM
// background threads, and the race-prevention/shutdown logic used there is
// brittle. Since this service is critical to things like downloading updates,
// we take no chances.) Also, by doing things this way, we avoid the need for
// locks, since gCertVerificationThreadPool is only ever accessed on the socket
// transport thread.
void InitializeSSLServerCertVerificationThreads() {
  // TODO: tuning, make parameters preferences
  gCertVerificationThreadPool = new nsThreadPool();
  NS_ADDREF(gCertVerificationThreadPool);

  (void)gCertVerificationThreadPool->SetIdleThreadLimit(5);
  (void)gCertVerificationThreadPool->SetIdleThreadTimeout(30 * 1000);
  (void)gCertVerificationThreadPool->SetThreadLimit(5);
  (void)gCertVerificationThreadPool->SetName("SSL Cert"_ns);
}

// Called when the socket transport thread finishes, to destroy the thread
// pool. Since the socket transport service has stopped processing events, it
// will not attempt any more SSL I/O operations, so it is clearly safe to shut
// down the SSL cert verification infrastructure. Also, the STS will not
// dispatch many SSL verification result events at this point, so any pending
// cert verifications will (correctly) fail at the point they are dispatched.
//
// The other shutdown race condition that is possible is a race condition with
// shutdown of the nsNSSComponent service. We use the
// nsNSSShutdownPreventionLock where needed (not here) to prevent that.
void StopSSLServerCertVerificationThreads() {
  if (gCertVerificationThreadPool) {
    gCertVerificationThreadPool->Shutdown();
    NS_RELEASE(gCertVerificationThreadPool);
  }
}

// A probe value of 1 means "no error".
uint32_t MapOverridableErrorToProbeValue(PRErrorCode errorCode) {
  switch (errorCode) {
    case SEC_ERROR_UNKNOWN_ISSUER:
      return 2;
    case SEC_ERROR_CA_CERT_INVALID:
      return 3;
    case SEC_ERROR_UNTRUSTED_ISSUER:
      return 4;
    case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE:
      return 5;
    case SEC_ERROR_UNTRUSTED_CERT:
      return 6;
    case SEC_ERROR_INADEQUATE_KEY_USAGE:
      return 7;
    case SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED:
      return 8;
    case SSL_ERROR_BAD_CERT_DOMAIN:
      return 9;
    case SEC_ERROR_EXPIRED_CERTIFICATE:
      return 10;
    case mozilla::pkix::MOZILLA_PKIX_ERROR_CA_CERT_USED_AS_END_ENTITY:
      return 11;
    case mozilla::pkix::MOZILLA_PKIX_ERROR_V1_CERT_USED_AS_CA:
      return 12;
    case mozilla::pkix::MOZILLA_PKIX_ERROR_INADEQUATE_KEY_SIZE:
      return 13;
    case mozilla::pkix::MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE:
      return 14;
    case mozilla::pkix::MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE:
      return 15;
    case SEC_ERROR_INVALID_TIME:
      return 16;
    case mozilla::pkix::MOZILLA_PKIX_ERROR_EMPTY_ISSUER_NAME:
      return 17;
    case mozilla::pkix::MOZILLA_PKIX_ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED:
      return 18;
    case mozilla::pkix::MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT:
      return 19;
    case mozilla::pkix::MOZILLA_PKIX_ERROR_MITM_DETECTED:
      return 20;
  }
  NS_WARNING(
      "Unknown certificate error code. Does MapOverridableErrorToProbeValue "
      "handle everything in CategorizeCertificateError?");
  return 0;
}

static uint32_t MapCertErrorToProbeValue(PRErrorCode errorCode) {
  uint32_t probeValue;
  switch (errorCode) {
    // see security/pkix/include/pkix/Result.h
#define MOZILLA_PKIX_MAP(name, value, nss_name) \
  case nss_name:                                \
    probeValue = value;                         \
    break;
    MOZILLA_PKIX_MAP_LIST
#undef MOZILLA_PKIX_MAP
    default:
      return 0;
  }

  // Since FATAL_ERROR_FLAG is 0x800, fatal error values are much larger than
  // non-fatal error values. To conserve space, we remap these so they start at
  // (decimal) 90 instead of 0x800. Currently there are ~50 non-fatal errors
  // mozilla::pkix might return, so saving space for 90 should be sufficient
  // (similarly, there are 4 fatal errors, so saving space for 10 should also
  // be sufficient).
  static_assert(
      FATAL_ERROR_FLAG == 0x800,
      "mozilla::pkix::FATAL_ERROR_FLAG is not what we were expecting");
  if (probeValue & FATAL_ERROR_FLAG) {
    probeValue ^= FATAL_ERROR_FLAG;
    probeValue += 90;
  }
  return probeValue;
}

// If the given PRErrorCode is an overridable certificate error, return which
// category (trust, time, domain mismatch) it falls in. If it is not
// overridable, return Nothing.
Maybe<nsITransportSecurityInfo::OverridableErrorCategory>
CategorizeCertificateError(PRErrorCode certificateError) {
  switch (certificateError) {
    case SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED:
    case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE:
    case SEC_ERROR_UNKNOWN_ISSUER:
    case SEC_ERROR_CA_CERT_INVALID:
    case mozilla::pkix::MOZILLA_PKIX_ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED:
    case mozilla::pkix::MOZILLA_PKIX_ERROR_CA_CERT_USED_AS_END_ENTITY:
    case mozilla::pkix::MOZILLA_PKIX_ERROR_EMPTY_ISSUER_NAME:
    case mozilla::pkix::MOZILLA_PKIX_ERROR_INADEQUATE_KEY_SIZE:
    case mozilla::pkix::MOZILLA_PKIX_ERROR_MITM_DETECTED:
    case mozilla::pkix::MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE:
    case mozilla::pkix::MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT:
    case mozilla::pkix::MOZILLA_PKIX_ERROR_V1_CERT_USED_AS_CA:
      return Some(
          nsITransportSecurityInfo::OverridableErrorCategory::ERROR_TRUST);

    case SSL_ERROR_BAD_CERT_DOMAIN:
      return Some(
          nsITransportSecurityInfo::OverridableErrorCategory::ERROR_DOMAIN);

    case SEC_ERROR_INVALID_TIME:
    case SEC_ERROR_EXPIRED_CERTIFICATE:
    case mozilla::pkix::MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE:
      return Some(
          nsITransportSecurityInfo::OverridableErrorCategory::ERROR_TIME);

    default:
      break;
  }
  return Nothing();
}

// Helper function to determine if overrides are allowed for this host.
// Overrides are not allowed for known HSTS hosts or hosts with pinning
// information. However, IP addresses can never be HSTS hosts and don't have
// pinning information.
static nsresult OverrideAllowedForHost(
    uint64_t aPtrForLog, const nsACString& aHostname,
    const OriginAttributes& aOriginAttributes, /*out*/ bool& aOverrideAllowed) {
  aOverrideAllowed = false;

  // If this is an IP address, overrides are allowed, because an IP address is
  // never an HSTS host. nsISiteSecurityService takes this into account
  // already, but the real problem here is that calling NS_NewURI with an IPv6
  // address fails. We do this to avoid that. A more comprehensive fix would be
  // to have Necko provide an nsIURI to PSM and to use that here (and
  // everywhere). However, that would be a wide-spanning change.
  if (net_IsValidIPv6Addr(aHostname)) {
    aOverrideAllowed = true;
    return NS_OK;
  }

  // If this is an HTTP Strict Transport Security host or a pinned host and the
  // certificate is bad, don't allow overrides (RFC 6797 section 12.1).
  bool strictTransportSecurityEnabled = false;
  bool isStaticallyPinned = false;
  nsCOMPtr<nsISiteSecurityService> sss(do_GetService(NS_SSSERVICE_CONTRACTID));
  if (!sss) {
    MOZ_LOG(
        gPIPNSSLog, LogLevel::Debug,
        ("[0x%" PRIx64 "] Couldn't get nsISiteSecurityService to check HSTS",
         aPtrForLog));
    return NS_ERROR_FAILURE;
  }

  nsCOMPtr<nsIURI> uri;
  nsresult rv = NS_NewURI(getter_AddRefs(uri), "https://"_ns + aHostname);
  if (NS_FAILED(rv)) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
            ("[0x%" PRIx64 "] Creating new URI failed", aPtrForLog));
    return rv;
  }

  rv =
      sss->IsSecureURI(uri, aOriginAttributes, &strictTransportSecurityEnabled);
  if (NS_FAILED(rv)) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
            ("[0x%" PRIx64 "] checking for HSTS failed", aPtrForLog));
    return rv;
  }

  nsCOMPtr<nsIPublicKeyPinningService> pkps =
      do_GetService(NS_PKPSERVICE_CONTRACTID, &rv);
  if (!pkps) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
            ("[0x%" PRIx64
             "] Couldn't get nsIPublicKeyPinningService to check pinning",
             aPtrForLog));
    return NS_ERROR_FAILURE;
  }
  rv = pkps->HostHasPins(uri, &isStaticallyPinned);
  if (NS_FAILED(rv)) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
            ("[0x%" PRIx64 "] checking for static pin failed", aPtrForLog));
    return rv;
  }

  aOverrideAllowed = !strictTransportSecurityEnabled && !isStaticallyPinned;
  return NS_OK;
}

// This function assumes that we will only use the SPDY connection coalescing
// feature on connections where we have negotiated SPDY using NPN. If we ever
// talk SPDY without having negotiated it with SPDY, this code will give wrong
// and perhaps unsafe results.
//
// Returns SECSuccess on the initial handshake of all connections, on
// renegotiations for any connections where we did not negotiate SPDY, or on any
// SPDY connection where the server's certificate did not change.
//
// Prohibit changing the server cert only if we negotiated SPDY,
// in order to support SPDY's cross-origin connection pooling.
static SECStatus BlockServerCertChangeForSpdy(
    NSSSocketControl* socketControl, const UniqueCERTCertificate& serverCert) {
  if (!socketControl->IsHandshakeCompleted()) {
    // first handshake on this connection, not a
    // renegotiation.
    return SECSuccess;
  }

  // Filter out sockets that did not neogtiate SPDY via NPN
  nsCOMPtr<nsITransportSecurityInfo> securityInfo;
  nsresult rv = socketControl->GetSecurityInfo(getter_AddRefs(securityInfo));
  MOZ_ASSERT(NS_SUCCEEDED(rv), "GetSecurityInfo() failed during renegotiation");
  if (NS_FAILED(rv) || !securityInfo) {
    PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
    return SECFailure;
  }
  nsAutoCString negotiatedNPN;
  rv = securityInfo->GetNegotiatedNPN(negotiatedNPN);
  MOZ_ASSERT(NS_SUCCEEDED(rv),
             "GetNegotiatedNPN() failed during renegotiation");

  if (NS_SUCCEEDED(rv) && !StringBeginsWith(negotiatedNPN, "spdy/"_ns)) {
    return SECSuccess;
  }
  // If GetNegotiatedNPN() failed we will assume spdy for safety's safe
  if (NS_FAILED(rv)) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
            ("BlockServerCertChangeForSpdy failed GetNegotiatedNPN() call."
             " Assuming spdy."));
  }

  // Check to see if the cert has actually changed
  nsCOMPtr<nsIX509Cert> cert(socketControl->GetServerCert());
  if (!cert) {
    PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
    return SECFailure;
  }
  nsTArray<uint8_t> certDER;
  if (NS_FAILED(cert->GetRawDER(certDER))) {
    PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
    return SECFailure;
  }
  if (certDER.Length() == serverCert->derCert.len &&
      memcmp(certDER.Elements(), serverCert->derCert.data, certDER.Length()) ==
          0) {
    return SECSuccess;
  }

  // Report an error - changed cert is confirmed
  MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
          ("SPDY refused to allow new cert during renegotiation"));
  PR_SetError(SSL_ERROR_RENEGOTIATION_NOT_ALLOWED, 0);
  return SECFailure;
}

void GatherTelemetryForSingleSCT(const ct::VerifiedSCT& verifiedSct) {
  // See SSL_SCTS_ORIGIN in Histograms.json.
  uint32_t origin = 0;
  switch (verifiedSct.origin) {
    case ct::VerifiedSCT::Origin::Embedded:
      origin = 1;
      break;
    case ct::VerifiedSCT::Origin::TLSExtension:
      origin = 2;
      break;
    case ct::VerifiedSCT::Origin::OCSPResponse:
      origin = 3;
      break;
    default:
      MOZ_ASSERT_UNREACHABLE("Unexpected VerifiedSCT::Origin type");
  }
  Telemetry::Accumulate(Telemetry::SSL_SCTS_ORIGIN, origin);

  // See SSL_SCTS_VERIFICATION_STATUS in Histograms.json.
  uint32_t verificationStatus = 0;
  switch (verifiedSct.status) {
    case ct::VerifiedSCT::Status::Valid:
      verificationStatus = 1;
      break;
    case ct::VerifiedSCT::Status::UnknownLog:
      verificationStatus = 2;
      break;
    case ct::VerifiedSCT::Status::InvalidSignature:
      verificationStatus = 3;
      break;
    case ct::VerifiedSCT::Status::InvalidTimestamp:
      verificationStatus = 4;
      break;
    case ct::VerifiedSCT::Status::ValidFromDisqualifiedLog:
      verificationStatus = 5;
      break;
    default:
      MOZ_ASSERT_UNREACHABLE("Unexpected VerifiedSCT::Status type");
  }
  Telemetry::Accumulate(Telemetry::SSL_SCTS_VERIFICATION_STATUS,
                        verificationStatus);
}

void GatherCertificateTransparencyTelemetry(
    const nsTArray<uint8_t>& rootCert, bool isEV,
    const CertificateTransparencyInfo& info) {
  if (!info.enabled) {
    // No telemetry is gathered when CT is disabled.
    return;
  }

  for (const ct::VerifiedSCT& sct : info.verifyResult.verifiedScts) {
    GatherTelemetryForSingleSCT(sct);
  }

  // Decoding errors are reported to the 0th bucket
  // of the SSL_SCTS_VERIFICATION_STATUS enumerated probe.
  for (size_t i = 0; i < info.verifyResult.decodingErrors; ++i) {
    Telemetry::Accumulate(Telemetry::SSL_SCTS_VERIFICATION_STATUS, 0);
  }

  // Handle the histogram of SCTs counts.
  uint32_t sctsCount =
      static_cast<uint32_t>(info.verifyResult.verifiedScts.size());
  // Note that sctsCount can also be 0 in case we've received SCT binary data,
  // but it failed to parse (e.g. due to unsupported CT protocol version).
  Telemetry::Accumulate(Telemetry::SSL_SCTS_PER_CONNECTION, sctsCount);

  // Report CT Policy compliance of EV certificates.
  if (isEV) {
    uint32_t evCompliance = 0;
    switch (info.policyCompliance) {
      case ct::CTPolicyCompliance::Compliant:
        evCompliance = 1;
        break;
      case ct::CTPolicyCompliance::NotEnoughScts:
        evCompliance = 2;
        break;
      case ct::CTPolicyCompliance::NotDiverseScts:
        evCompliance = 3;
        break;
      case ct::CTPolicyCompliance::Unknown:
      default:
        MOZ_ASSERT_UNREACHABLE("Unexpected CTPolicyCompliance type");
    }
    Telemetry::Accumulate(Telemetry::SSL_CT_POLICY_COMPLIANCE_OF_EV_CERTS,
                          evCompliance);
  }

  // Report CT Policy compliance by CA.
  switch (info.policyCompliance) {
    case ct::CTPolicyCompliance::Compliant:
      AccumulateTelemetryForRootCA(
          Telemetry::SSL_CT_POLICY_COMPLIANT_CONNECTIONS_BY_CA, rootCert);
      break;
    case ct::CTPolicyCompliance::NotEnoughScts:
    case ct::CTPolicyCompliance::NotDiverseScts:
      AccumulateTelemetryForRootCA(
          Telemetry::SSL_CT_POLICY_NON_COMPLIANT_CONNECTIONS_BY_CA, rootCert);
      break;
    case ct::CTPolicyCompliance::Unknown:
    default:
      MOZ_ASSERT_UNREACHABLE("Unexpected CTPolicyCompliance type");
  }
}

// This function collects telemetry about certs. It will be called on one of
// CertVerificationThread. When the socket process is used this will be called
// on the parent process.
static void CollectCertTelemetry(
    mozilla::pkix::Result aCertVerificationResult, EVStatus aEVStatus,
    CertVerifier::OCSPStaplingStatus aOcspStaplingStatus,
    KeySizeStatus aKeySizeStatus,
    const PinningTelemetryInfo& aPinningTelemetryInfo,
    const nsTArray<nsTArray<uint8_t>>& aBuiltCertChain,
    const CertificateTransparencyInfo& aCertificateTransparencyInfo) {
  uint32_t evStatus = (aCertVerificationResult != Success) ? 0  // 0 = Failure
                      : (aEVStatus != EVStatus::EV)        ? 1  // 1 = DV
                                                           : 2;        // 2 = EV
  Telemetry::Accumulate(Telemetry::CERT_EV_STATUS, evStatus);

  if (aOcspStaplingStatus != CertVerifier::OCSP_STAPLING_NEVER_CHECKED) {
    Telemetry::Accumulate(Telemetry::SSL_OCSP_STAPLING, aOcspStaplingStatus);
  }

  if (aKeySizeStatus != KeySizeStatus::NeverChecked) {
    Telemetry::Accumulate(Telemetry::CERT_CHAIN_KEY_SIZE_STATUS,
                          static_cast<uint32_t>(aKeySizeStatus));
  }

  if (aPinningTelemetryInfo.accumulateForRoot) {
    Telemetry::Accumulate(Telemetry::CERT_PINNING_FAILURES_BY_CA,
                          aPinningTelemetryInfo.rootBucket);
  }

  if (aPinningTelemetryInfo.accumulateResult) {
    MOZ_ASSERT(aPinningTelemetryInfo.certPinningResultHistogram.isSome());
    Telemetry::Accumulate(
        aPinningTelemetryInfo.certPinningResultHistogram.value(),
        aPinningTelemetryInfo.certPinningResultBucket);
  }

  if (aCertVerificationResult == Success && aBuiltCertChain.Length() > 0) {
    const nsTArray<uint8_t>& rootCert = aBuiltCertChain.LastElement();
    AccumulateTelemetryForRootCA(Telemetry::CERT_VALIDATION_SUCCESS_BY_CA,
                                 rootCert);
    GatherCertificateTransparencyTelemetry(rootCert, aEVStatus == EVStatus::EV,
                                           aCertificateTransparencyInfo);
  }
}

// Note: Takes ownership of |peerCertChain| if SECSuccess is not returned.
Result AuthCertificate(
    CertVerifier& certVerifier, void* aPinArg,
    const nsTArray<uint8_t>& certBytes,
    const nsTArray<nsTArray<uint8_t>>& peerCertChain,
    const nsACString& aHostName, const OriginAttributes& aOriginAttributes,
    const Maybe<nsTArray<uint8_t>>& stapledOCSPResponse,
    const Maybe<nsTArray<uint8_t>>& sctsFromTLSExtension,
    const Maybe<DelegatedCredentialInfo>& dcInfo, uint32_t providerFlags,
    Time time, uint32_t certVerifierFlags,
    /*out*/ nsTArray<nsTArray<uint8_t>>& builtCertChain,
    /*out*/ EVStatus& evStatus,
    /*out*/ CertificateTransparencyInfo& certificateTransparencyInfo,
    /*out*/ bool& aIsBuiltCertChainRootBuiltInRoot,
    /*out*/ bool& aMadeOCSPRequests) {
  CertVerifier::OCSPStaplingStatus ocspStaplingStatus =
      CertVerifier::OCSP_STAPLING_NEVER_CHECKED;
  KeySizeStatus keySizeStatus = KeySizeStatus::NeverChecked;
  PinningTelemetryInfo pinningTelemetryInfo;

  nsTArray<nsTArray<uint8_t>> peerCertsBytes;
  // Don't include the end-entity certificate.
  if (!peerCertChain.IsEmpty()) {
    std::transform(
        peerCertChain.cbegin() + 1, peerCertChain.cend(),
        MakeBackInserter(peerCertsBytes),
        [](const auto& elementArray) { return elementArray.Clone(); });
  }

  Result rv = certVerifier.VerifySSLServerCert(
      certBytes, time, aPinArg, aHostName, builtCertChain, certVerifierFlags,
      Some(std::move(peerCertsBytes)), stapledOCSPResponse,
      sctsFromTLSExtension, dcInfo, aOriginAttributes, &evStatus,
      &ocspStaplingStatus, &keySizeStatus, &pinningTelemetryInfo,
      &certificateTransparencyInfo, &aIsBuiltCertChainRootBuiltInRoot,
      &aMadeOCSPRequests);

  CollectCertTelemetry(rv, evStatus, ocspStaplingStatus, keySizeStatus,
                       pinningTelemetryInfo, builtCertChain,
                       certificateTransparencyInfo);

  return rv;
}

PRErrorCode AuthCertificateParseResults(
    uint64_t aPtrForLog, const nsACString& aHostName, int32_t aPort,
    const OriginAttributes& aOriginAttributes,
    const nsCOMPtr<nsIX509Cert>& aCert, mozilla::pkix::Time aTime,
    PRErrorCode aCertVerificationError,
    /* out */
    nsITransportSecurityInfo::OverridableErrorCategory&
        aOverridableErrorCategory) {
  uint32_t probeValue = MapCertErrorToProbeValue(aCertVerificationError);
  Telemetry::Accumulate(Telemetry::SSL_CERT_VERIFICATION_ERRORS, probeValue);

  Maybe<nsITransportSecurityInfo::OverridableErrorCategory>
      maybeOverridableErrorCategory =
          CategorizeCertificateError(aCertVerificationError);
  // If this isn't an overridable error, return it now. This will stop the
  // connection and report the given error.
  if (!maybeOverridableErrorCategory.isSome()) {
    return aCertVerificationError;
  }
  aOverridableErrorCategory = *maybeOverridableErrorCategory;

  bool overrideAllowed = false;
  nsresult rv = OverrideAllowedForHost(aPtrForLog, aHostName, aOriginAttributes,
                                       overrideAllowed);
  if (NS_FAILED(rv)) {
    return aCertVerificationError;
  }

  if (!overrideAllowed) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
            ("[0x%" PRIx64 "] HSTS or pinned host - no overrides allowed",
             aPtrForLog));
    return aCertVerificationError;
  }

  nsCOMPtr<nsICertOverrideService> overrideService =
      do_GetService(NS_CERTOVERRIDE_CONTRACTID);
  if (!overrideService) {
    return aCertVerificationError;
  }
  bool haveOverride;
  bool isTemporaryOverride;
  rv = overrideService->HasMatchingOverride(aHostName, aPort, aOriginAttributes,
                                            aCert, &isTemporaryOverride,
                                            &haveOverride);
  if (NS_FAILED(rv)) {
    return aCertVerificationError;
  }
  Unused << isTemporaryOverride;
  if (haveOverride) {
    uint32_t probeValue =
        MapOverridableErrorToProbeValue(aCertVerificationError);
    Telemetry::Accumulate(Telemetry::SSL_CERT_ERROR_OVERRIDES, probeValue);
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
            ("[0x%" PRIx64 "] certificate error overridden", aPtrForLog));
    return 0;
  }

  return aCertVerificationError;
}

static nsTArray<nsTArray<uint8_t>> CreateCertBytesArray(
    const UniqueCERTCertList& aCertChain) {
  nsTArray<nsTArray<uint8_t>> certsBytes;
  for (CERTCertListNode* n = CERT_LIST_HEAD(aCertChain);
       !CERT_LIST_END(n, aCertChain); n = CERT_LIST_NEXT(n)) {
    nsTArray<uint8_t> certBytes;
    certBytes.AppendElements(n->cert->derCert.data, n->cert->derCert.len);
    certsBytes.AppendElement(std::move(certBytes));
  }
  return certsBytes;
}

/*static*/
SECStatus SSLServerCertVerificationJob::Dispatch(
    uint64_t addrForLogging, void* aPinArg,
    nsTArray<nsTArray<uint8_t>>&& peerCertChain, const nsACString& aHostName,
    int32_t aPort, const OriginAttributes& aOriginAttributes,
    Maybe<nsTArray<uint8_t>>& stapledOCSPResponse,
    Maybe<nsTArray<uint8_t>>& sctsFromTLSExtension,
    Maybe<DelegatedCredentialInfo>& dcInfo, uint32_t providerFlags, Time time,
    uint32_t certVerifierFlags,
    BaseSSLServerCertVerificationResult* aResultTask) {
  // Runs on the socket transport thread
  if (!aResultTask || peerCertChain.IsEmpty()) {
    MOZ_ASSERT_UNREACHABLE(
        "must have result task and non-empty peer cert chain");
    PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
    return SECFailure;
  }

  if (!gCertVerificationThreadPool) {
    PR_SetError(PR_INVALID_STATE_ERROR, 0);
    return SECFailure;
  }

  RefPtr<SSLServerCertVerificationJob> job(new SSLServerCertVerificationJob(
      addrForLogging, aPinArg, std::move(peerCertChain), aHostName, aPort,
      aOriginAttributes, stapledOCSPResponse, sctsFromTLSExtension, dcInfo,
      providerFlags, time, certVerifierFlags, aResultTask));

  nsresult nrv = gCertVerificationThreadPool->Dispatch(job, NS_DISPATCH_NORMAL);
  if (NS_FAILED(nrv)) {
    // We can't call SetCertVerificationResult here to change
    // mCertVerificationState because SetCertVerificationResult will call
    // libssl functions that acquire SSL locks that are already being held at
    // this point. However, we can set an error with PR_SetError and return
    // SECFailure, and the correct thing will happen (the error will be
    // propagated and this connection will be terminated).
    PRErrorCode error = nrv == NS_ERROR_OUT_OF_MEMORY ? PR_OUT_OF_MEMORY_ERROR
                                                      : PR_INVALID_STATE_ERROR;
    PR_SetError(error, 0);
    return SECFailure;
  }

  PR_SetError(PR_WOULD_BLOCK_ERROR, 0);
  return SECWouldBlock;
}

NS_IMETHODIMP
SSLServerCertVerificationJob::Run() {
  // Runs on a cert verification thread and only on parent process.
  MOZ_ASSERT(XRE_IsParentProcess());

  MOZ_LOG(
      gPIPNSSLog, LogLevel::Debug,
      ("[%" PRIx64 "] SSLServerCertVerificationJob::Run\n", mAddrForLogging));

  RefPtr<SharedCertVerifier> certVerifier(GetDefaultCertVerifier());
  if (!certVerifier) {
    PR_SetError(SEC_ERROR_NOT_INITIALIZED, 0);
    return NS_OK;
  }

  TimeStamp jobStartTime = TimeStamp::Now();
  EVStatus evStatus;
  CertificateTransparencyInfo certificateTransparencyInfo;
  bool isCertChainRootBuiltInRoot = false;
  bool madeOCSPRequests = false;
  nsTArray<nsTArray<uint8_t>> builtChainBytesArray;
  nsTArray<uint8_t> certBytes(mPeerCertChain.ElementAt(0).Clone());
  Result rv = AuthCertificate(
      *certVerifier, mPinArg, certBytes, mPeerCertChain, mHostName,
      mOriginAttributes, mStapledOCSPResponse, mSCTsFromTLSExtension, mDCInfo,
      mProviderFlags, mTime, mCertVerifierFlags, builtChainBytesArray, evStatus,
      certificateTransparencyInfo, isCertChainRootBuiltInRoot,
      madeOCSPRequests);

  if (rv == Success) {
    Telemetry::AccumulateTimeDelta(
        Telemetry::SSL_SUCCESFUL_CERT_VALIDATION_TIME_MOZILLAPKIX, jobStartTime,
        TimeStamp::Now());
    Telemetry::Accumulate(Telemetry::SSL_CERT_ERROR_OVERRIDES, 1);

    mResultTask->Dispatch(
        std::move(builtChainBytesArray), std::move(mPeerCertChain),
        TransportSecurityInfo::ConvertCertificateTransparencyInfoToStatus(
            certificateTransparencyInfo),
        evStatus, true, 0,
        nsITransportSecurityInfo::OverridableErrorCategory::ERROR_UNSET,
        isCertChainRootBuiltInRoot, mProviderFlags, madeOCSPRequests);
    return NS_OK;
  }

  Telemetry::AccumulateTimeDelta(
      Telemetry::SSL_INITIAL_FAILED_CERT_VALIDATION_TIME_MOZILLAPKIX,
      jobStartTime, TimeStamp::Now());

  PRErrorCode error = MapResultToPRErrorCode(rv);
  nsITransportSecurityInfo::OverridableErrorCategory overridableErrorCategory =
      nsITransportSecurityInfo::OverridableErrorCategory::ERROR_UNSET;
  nsCOMPtr<nsIX509Cert> cert(new nsNSSCertificate(std::move(certBytes)));
  PRErrorCode finalError = AuthCertificateParseResults(
      mAddrForLogging, mHostName, mPort, mOriginAttributes, cert, mTime, error,
      overridableErrorCategory);

  // NB: finalError may be 0 here, in which the connection will continue.
  mResultTask->Dispatch(
      std::move(builtChainBytesArray), std::move(mPeerCertChain),
      nsITransportSecurityInfo::CERTIFICATE_TRANSPARENCY_NOT_APPLICABLE,
      EVStatus::NotEV, false, finalError, overridableErrorCategory, false,
      mProviderFlags, madeOCSPRequests);
  return NS_OK;
}

// Takes information needed for cert verification, does some consistency
//  checks and calls SSLServerCertVerificationJob::Dispatch.
SECStatus AuthCertificateHookInternal(
    CommonSocketControl* socketControl, const void* aPtrForLogging,
    const nsACString& hostName, nsTArray<nsTArray<uint8_t>>&& peerCertChain,
    Maybe<nsTArray<uint8_t>>& stapledOCSPResponse,
    Maybe<nsTArray<uint8_t>>& sctsFromTLSExtension,
    Maybe<DelegatedCredentialInfo>& dcInfo, uint32_t providerFlags,
    uint32_t certVerifierFlags) {
  // Runs on the socket transport thread

  MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
          ("[%p] starting AuthCertificateHookInternal\n", aPtrForLogging));

  if (!socketControl || peerCertChain.IsEmpty()) {
    PR_SetError(PR_INVALID_STATE_ERROR, 0);
    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;
  }

  MOZ_ASSERT(onSTSThread);

  if (!onSTSThread) {
    PR_SetError(PR_INVALID_STATE_ERROR, 0);
    return SECFailure;
  }

  uint64_t addr = reinterpret_cast<uintptr_t>(aPtrForLogging);
  RefPtr<SSLServerCertVerificationResult> resultTask =
      new SSLServerCertVerificationResult(socketControl);

  if (XRE_IsSocketProcess()) {
    return RemoteProcessCertVerification(
        std::move(peerCertChain), hostName, socketControl->GetPort(),
        socketControl->GetOriginAttributes(), stapledOCSPResponse,
        sctsFromTLSExtension, dcInfo, providerFlags, certVerifierFlags,
        resultTask);
  }

  // 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.
  return SSLServerCertVerificationJob::Dispatch(
      addr, socketControl, std::move(peerCertChain), hostName,
      socketControl->GetPort(), socketControl->GetOriginAttributes(),
      stapledOCSPResponse, sctsFromTLSExtension, dcInfo, providerFlags, Now(),
      certVerifierFlags, resultTask);
}

// Extracts whatever information we need out of fd (using SSL_*) and passes it
// to AuthCertificateHookInternal. AuthCertificateHookInternal will call
// SSLServerCertVerificationJob::Dispatch. SSLServerCertVerificationJob
// should never do anything with fd except logging.
SECStatus AuthCertificateHook(void* arg, PRFileDesc* fd, PRBool checkSig,
                              PRBool isServer) {
  MOZ_LOG(gPIPNSSLog, LogLevel::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.
  MOZ_ASSERT(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.
  MOZ_ASSERT(!isServer, "AuthCertificateHook: isServer unexpectedly true");

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

  UniqueCERTCertificate serverCert(SSL_PeerCertificate(fd));

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

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

  UniqueCERTCertList peerCertChain(SSL_PeerCertificateChain(fd));
  if (!peerCertChain) {
    PR_SetError(PR_INVALID_STATE_ERROR, 0);
    return SECFailure;
  }

  nsTArray<nsTArray<uint8_t>> peerCertsBytes =
      CreateCertBytesArray(peerCertChain);

  // 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);
  Maybe<nsTArray<uint8_t>> stapledOCSPResponse;
  // we currently only support single stapled responses
  if (csa && csa->len == 1) {
    stapledOCSPResponse.emplace();
    stapledOCSPResponse->SetCapacity(csa->items[0].len);
    stapledOCSPResponse->AppendElements(csa->items[0].data, csa->items[0].len);
  }

  Maybe<nsTArray<uint8_t>> sctsFromTLSExtension;
  const SECItem* sctsFromTLSExtensionSECItem = SSL_PeerSignedCertTimestamps(fd);
  if (sctsFromTLSExtensionSECItem) {
    sctsFromTLSExtension.emplace();
    sctsFromTLSExtension->SetCapacity(sctsFromTLSExtensionSECItem->len);
    sctsFromTLSExtension->AppendElements(sctsFromTLSExtensionSECItem->data,
                                         sctsFromTLSExtensionSECItem->len);
  }

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

  uint32_t certVerifierFlags = 0;
  if (!socketInfo->SharedState().IsOCSPStaplingEnabled() ||
      !socketInfo->SharedState().IsOCSPMustStapleEnabled()) {
    certVerifierFlags |= CertVerifier::FLAG_TLS_IGNORE_STATUS_REQUEST;
  }

  // Get DC information
  Maybe<DelegatedCredentialInfo> dcInfo;
  SSLPreliminaryChannelInfo channelPreInfo;
  SECStatus rv = SSL_GetPreliminaryChannelInfo(fd, &channelPreInfo,
                                               sizeof(channelPreInfo));
  if (rv != SECSuccess) {
    PR_SetError(PR_INVALID_STATE_ERROR, 0);
    return SECFailure;
  }
  if (channelPreInfo.peerDelegCred) {
    dcInfo.emplace(DelegatedCredentialInfo(channelPreInfo.signatureScheme,
                                           channelPreInfo.authKeyBits));
  }

  // If we configured an ECHConfig and NSS returned the public name
  // for verification, ECH was rejected. Proceed, verifying to the
  // public name. The result determines how NSS will fail (i.e. with
  // any provided retry_configs if successful). See draft-ietf-tls-esni-08.
  nsCString echConfig;
  nsresult nsrv = socketInfo->GetEchConfig(echConfig);
  bool verifyToEchPublicName =
      NS_SUCCEEDED(nsrv) && echConfig.Length() && channelPreInfo.echPublicName;

  const nsCString echPublicName(channelPreInfo.echPublicName);
  const nsACString& hostname =
      verifyToEchPublicName ? echPublicName : socketInfo->GetHostName();
  socketInfo->SetCertVerificationWaiting();
  rv = AuthCertificateHookInternal(socketInfo, static_cast<const void*>(fd),
                                   hostname, std::move(peerCertsBytes),
                                   stapledOCSPResponse, sctsFromTLSExtension,
                                   dcInfo, providerFlags, certVerifierFlags);
  return rv;
}

// Takes information needed for cert verification, does some consistency
// checks and calls SSLServerCertVerificationJob::Dispatch.
// This function is used for Quic.
SECStatus AuthCertificateHookWithInfo(
    CommonSocketControl* socketControl, const nsACString& aHostName,
    const void* aPtrForLogging, nsTArray<nsTArray<uint8_t>>&& peerCertChain,
    Maybe<nsTArray<nsTArray<uint8_t>>>& stapledOCSPResponses,
    Maybe<nsTArray<uint8_t>>& sctsFromTLSExtension, uint32_t providerFlags) {
  if (peerCertChain.IsEmpty()) {
    PR_SetError(PR_INVALID_STATE_ERROR, 0);
    return SECFailure;
  }

  // we currently only support single stapled responses
  Maybe<nsTArray<uint8_t>> stapledOCSPResponse;
  if (stapledOCSPResponses && (stapledOCSPResponses->Length() == 1)) {
    stapledOCSPResponse.emplace(stapledOCSPResponses->ElementAt(0).Clone());
  }

  uint32_t certVerifierFlags = 0;
  // QuicSocketControl does not have a SharedState as NSSSocketControl.
  // Here we need prefs for ocsp. This are prefs they are the same for
  // PublicSSLState and PrivateSSLState, just take them from one of them.
  if (!PublicSSLState()->IsOCSPStaplingEnabled() ||
      !PublicSSLState()->IsOCSPMustStapleEnabled()) {
    certVerifierFlags |= CertVerifier::FLAG_TLS_IGNORE_STATUS_REQUEST;
  }

  // Need to update Quic stack to reflect the PreliminaryInfo fields
  // for Delegated Credentials.
  Maybe<DelegatedCredentialInfo> dcInfo;

  return AuthCertificateHookInternal(socketControl, aPtrForLogging, aHostName,
                                     std::move(peerCertChain),
                                     stapledOCSPResponse, sctsFromTLSExtension,
                                     dcInfo, providerFlags, certVerifierFlags);
}

NS_IMPL_ISUPPORTS_INHERITED0(SSLServerCertVerificationResult, Runnable)

SSLServerCertVerificationResult::SSLServerCertVerificationResult(
    CommonSocketControl* socketControl)
    : Runnable("psm::SSLServerCertVerificationResult"),
      mSocketControl(socketControl),
      mCertificateTransparencyStatus(0),
      mEVStatus(EVStatus::NotEV),
      mSucceeded(false),
      mFinalError(0),
      mOverridableErrorCategory(
          nsITransportSecurityInfo::OverridableErrorCategory::ERROR_UNSET),
      mProviderFlags(0) {}

void SSLServerCertVerificationResult::Dispatch(
    nsTArray<nsTArray<uint8_t>>&& aBuiltChain,
    nsTArray<nsTArray<uint8_t>>&& aPeerCertChain,
    uint16_t aCertificateTransparencyStatus, EVStatus aEVStatus,
    bool aSucceeded, PRErrorCode aFinalError,
    nsITransportSecurityInfo::OverridableErrorCategory
        aOverridableErrorCategory,
    bool aIsBuiltCertChainRootBuiltInRoot, uint32_t aProviderFlags,
    bool aMadeOCSPRequests) {
  mBuiltChain = std::move(aBuiltChain);
  mPeerCertChain = std::move(aPeerCertChain);
  mCertificateTransparencyStatus = aCertificateTransparencyStatus;
  mEVStatus = aEVStatus;
  mSucceeded = aSucceeded;
  mFinalError = aFinalError;
  mOverridableErrorCategory = aOverridableErrorCategory;
  mIsBuiltCertChainRootBuiltInRoot = aIsBuiltCertChainRootBuiltInRoot;
  mProviderFlags = aProviderFlags;
  mMadeOCSPRequests = aMadeOCSPRequests;

  if (mSucceeded && mBuiltChain.IsEmpty()) {
    MOZ_ASSERT_UNREACHABLE(
        "if the handshake succeeded, the built chain shouldn't be empty");
    mSucceeded = false;
    mFinalError = SEC_ERROR_LIBRARY_FAILURE;
  }
  if (!mSucceeded && mPeerCertChain.IsEmpty()) {
    MOZ_ASSERT_UNREACHABLE(
        "if the handshake failed, the peer chain shouldn't be empty");
    mFinalError = SEC_ERROR_LIBRARY_FAILURE;
  }

  nsresult rv;
  nsCOMPtr<nsIEventTarget> stsTarget =
      do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
  MOZ_ASSERT(stsTarget, "Failed to get socket transport service event target");
  rv = stsTarget->Dispatch(this, NS_DISPATCH_NORMAL);
  MOZ_ASSERT(NS_SUCCEEDED(rv),
             "Failed to dispatch SSLServerCertVerificationResult");
}

NS_IMETHODIMP
SSLServerCertVerificationResult::Run() {
#ifdef DEBUG
  bool onSTSThread = false;
  nsresult nrv;
  nsCOMPtr<nsIEventTarget> sts =
      do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &nrv);
  if (NS_SUCCEEDED(nrv)) {
    nrv = sts->IsOnCurrentThread(&onSTSThread);
  }

  MOZ_ASSERT(onSTSThread);
#endif

  if (mSucceeded && !XRE_IsSocketProcess() &&
      !(mProviderFlags & nsISocketProvider::NO_PERMANENT_STORAGE)) {
    // This dispatches an event that will run when the socket thread is idle.
    SaveIntermediateCerts(mBuiltChain);
  }

  mSocketControl->SetMadeOCSPRequests(mMadeOCSPRequests);

  if (mSucceeded) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
            ("SSLServerCertVerificationResult::Run setting NEW cert"));
    nsTArray<uint8_t> certBytes(mBuiltChain.ElementAt(0).Clone());
    nsCOMPtr<nsIX509Cert> cert(new nsNSSCertificate(std::move(certBytes)));
    mSocketControl->SetServerCert(cert, mEVStatus);
    mSocketControl->SetSucceededCertChain(std::move(mBuiltChain));

    mSocketControl->SetIsBuiltCertChainRootBuiltInRoot(
        mIsBuiltCertChainRootBuiltInRoot);
    mSocketControl->SetCertificateTransparencyStatus(
        mCertificateTransparencyStatus);
  } else {
    nsTArray<uint8_t> certBytes(mPeerCertChain.ElementAt(0).Clone());
    nsCOMPtr<nsIX509Cert> cert(new nsNSSCertificate(std::move(certBytes)));
    // Certificate validation failed; store the peer certificate chain on
    // mSocketControl so it can be used for error reporting.
    mSocketControl->SetFailedCertChain(std::move(mPeerCertChain));
    if (mOverridableErrorCategory !=
        nsITransportSecurityInfo::OverridableErrorCategory::ERROR_UNSET) {
      mSocketControl->SetStatusErrorBits(cert, mOverridableErrorCategory);
    }
  }

  mSocketControl->SetCertVerificationResult(mFinalError);
  return NS_OK;
}

}  // namespace psm
}  // namespace mozilla
