/* 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/. */

#include "nsNSSCertificateDB.h"

#include "CertVerifier.h"
#include "CryptoTask.h"
#include "ExtendedValidation.h"
#include "NSSCertDBTrustDomain.h"
#include "certdb.h"
#include "mozilla/Assertions.h"
#include "mozilla/Base64.h"
#include "mozilla/Casting.h"
#include "mozilla/Logging.h"
#include "mozilla/Services.h"
#include "mozpkix/Time.h"
#include "mozpkix/pkixnss.h"
#include "mozpkix/pkixtypes.h"
#include "nsArray.h"
#include "nsArrayUtils.h"
#include "nsCOMPtr.h"
#include "nsComponentManagerUtils.h"
#include "nsICertificateDialogs.h"
#include "nsIFile.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsIMutableArray.h"
#include "nsIObserverService.h"
#include "nsIPrompt.h"
#include "nsNSSCertHelper.h"
#include "nsNSSCertTrust.h"
#include "nsNSSCertificate.h"
#include "nsNSSComponent.h"
#include "nsPKCS12Blob.h"
#include "nsPromiseFlatString.h"
#include "nsProxyRelease.h"
#include "nsReadableUtils.h"
#include "nsThreadUtils.h"
#include "nspr.h"
#include "secasn1.h"
#include "secder.h"
#include "secerr.h"
#include "ssl.h"

#ifdef MOZ_WIDGET_ANDROID
#  include "mozilla/java/ClientAuthCertificateManagerWrappers.h"
#endif

#ifdef XP_WIN
#  include <winsock.h>  // for ntohl
#endif

using namespace mozilla;
using namespace mozilla::psm;

extern LazyLogModule gPIPNSSLog;

NS_IMPL_ISUPPORTS(nsNSSCertificateDB, nsIX509CertDB)

NS_IMETHODIMP
nsNSSCertificateDB::FindCertByDBKey(const nsACString& aDBKey,
                                    /*out*/ nsIX509Cert** _cert) {
  NS_ENSURE_ARG_POINTER(_cert);
  *_cert = nullptr;

  if (aDBKey.IsEmpty()) {
    return NS_ERROR_INVALID_ARG;
  }

  nsresult rv = BlockUntilLoadableCertsLoaded();
  if (NS_FAILED(rv)) {
    return rv;
  }

  UniqueCERTCertificate cert;
  rv = FindCertByDBKey(aDBKey, cert);
  if (NS_FAILED(rv)) {
    return rv;
  }
  // If we can't find the certificate, that's not an error. Just return null.
  if (!cert) {
    return NS_OK;
  }
  RefPtr nssCert = MakeRefPtr<nsNSSCertificate>(cert.get());
  nssCert.forget(_cert);
  return NS_OK;
}

nsresult nsNSSCertificateDB::FindCertByDBKey(const nsACString& aDBKey,
                                             UniqueCERTCertificate& cert) {
  static_assert(sizeof(uint64_t) == 8, "type size sanity check");
  static_assert(sizeof(uint32_t) == 4, "type size sanity check");
  // (From nsNSSCertificate::GetDbKey)
  // The format of the key is the base64 encoding of the following:
  // 4 bytes: {0, 0, 0, 0} (this was intended to be the module ID, but it was
  //                        never implemented)
  // 4 bytes: {0, 0, 0, 0} (this was intended to be the slot ID, but it was
  //                        never implemented)
  // 4 bytes: <serial number length in big-endian order>
  // 4 bytes: <DER-encoded issuer distinguished name length in big-endian order>
  // n bytes: <bytes of serial number>
  // m bytes: <DER-encoded issuer distinguished name>
  nsAutoCString decoded;
  nsAutoCString tmpDBKey(aDBKey);
  // Filter out any whitespace for backwards compatibility.
  tmpDBKey.StripWhitespace();
  nsresult rv = Base64Decode(tmpDBKey, decoded);
  if (NS_FAILED(rv)) {
    return rv;
  }
  if (decoded.Length() < 16) {
    return NS_ERROR_ILLEGAL_INPUT;
  }
  const char* reader = decoded.BeginReading();
  uint64_t zeroes = *BitwiseCast<const uint64_t*, const char*>(reader);
  if (zeroes != 0) {
    return NS_ERROR_ILLEGAL_INPUT;
  }
  reader += sizeof(uint64_t);
  // Note: We surround the ntohl() argument with parentheses to stop the macro
  //       from thinking two arguments were passed.
  uint32_t serialNumberLen =
      ntohl((*BitwiseCast<const uint32_t*, const char*>(reader)));
  reader += sizeof(uint32_t);
  uint32_t issuerLen =
      ntohl((*BitwiseCast<const uint32_t*, const char*>(reader)));
  reader += sizeof(uint32_t);
  if (decoded.Length() != 16ULL + serialNumberLen + issuerLen) {
    return NS_ERROR_ILLEGAL_INPUT;
  }
  CERTIssuerAndSN issuerSN;
  issuerSN.serialNumber.len = serialNumberLen;
  issuerSN.serialNumber.data = BitwiseCast<unsigned char*, const char*>(reader);
  reader += serialNumberLen;
  issuerSN.derIssuer.len = issuerLen;
  issuerSN.derIssuer.data = BitwiseCast<unsigned char*, const char*>(reader);
  reader += issuerLen;
  MOZ_ASSERT(reader == decoded.EndReading());

  AutoSearchingForClientAuthCertificates _;
  cert.reset(CERT_FindCertByIssuerAndSN(CERT_GetDefaultCertDB(), &issuerSN));
  return NS_OK;
}

SECStatus collect_certs(void* arg, SECItem** certs, int numcerts) {
  nsTArray<nsTArray<uint8_t>>* certsArray =
      reinterpret_cast<nsTArray<nsTArray<uint8_t>>*>(arg);

  while (numcerts--) {
    nsTArray<uint8_t> certArray;
    SECItem* cert = *certs;
    certArray.AppendElements(cert->data, cert->len);
    certsArray->AppendElement(std::move(certArray));
    certs++;
  }
  return (SECSuccess);
}

nsresult nsNSSCertificateDB::getCertsFromPackage(
    nsTArray<nsTArray<uint8_t>>& collectArgs, uint8_t* data, uint32_t length) {
  if (CERT_DecodeCertPackage(BitwiseCast<char*, uint8_t*>(data), length,
                             collect_certs, &collectArgs) != SECSuccess) {
    return NS_ERROR_FAILURE;
  }
  return NS_OK;
}

// When using the sql-backed softoken, trust settings are authenticated using a
// key in the secret database. Thus, if the user has a password, we need to
// authenticate to the token in order to be able to change trust settings.
SECStatus ChangeCertTrustWithPossibleAuthentication(
    const UniqueCERTCertificate& cert, CERTCertTrust& trust, void* ctx) {
  MOZ_ASSERT(cert, "cert must be non-null");
  if (!cert) {
    PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
    return SECFailure;
  }

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

  // NSS ignores the first argument to CERT_ChangeCertTrust
  SECStatus srv = CERT_ChangeCertTrust(nullptr, cert.get(), &trust);
  if (srv != SECSuccess && PR_GetError() != SEC_ERROR_TOKEN_NOT_LOGGED_IN) {
    return SECFailure;
  }
  if (srv == SECSuccess) {
    certVerifier->ClearTrustCache();
    return SECSuccess;
  }

  // CERT_ChangeCertTrust failed with SEC_ERROR_TOKEN_NOT_LOGGED_IN, so
  // authenticate and try again.
  if (cert->slot) {
    // If this certificate is on an external PKCS#11 token, we have to
    // authenticate to that token.
    srv = PK11_Authenticate(cert->slot, PR_TRUE, ctx);
  } else {
    // Otherwise, the certificate is on the internal module.
    UniquePK11SlotInfo internalSlot(PK11_GetInternalKeySlot());
    srv = PK11_Authenticate(internalSlot.get(), PR_TRUE, ctx);
  }
  if (srv != SECSuccess) {
    return srv;
  }
  srv = CERT_ChangeCertTrust(nullptr, cert.get(), &trust);
  if (srv != SECSuccess) {
    return srv;
  }

  certVerifier->ClearTrustCache();
  return SECSuccess;
}

static nsresult ImportCertsIntoPermanentStorage(
    const UniqueCERTCertList& certChain) {
  bool encounteredFailure = false;
  PRErrorCode savedErrorCode = 0;
  UniquePK11SlotInfo slot(PK11_GetInternalKeySlot());
  for (CERTCertListNode* chainNode = CERT_LIST_HEAD(certChain);
       !CERT_LIST_END(chainNode, certChain);
       chainNode = CERT_LIST_NEXT(chainNode)) {
    UniquePORTString nickname(CERT_MakeCANickname(chainNode->cert));
    SECStatus srv = PK11_ImportCert(slot.get(), chainNode->cert,
                                    CK_INVALID_HANDLE, nickname.get(),
                                    false);  // this parameter is ignored by NSS
    if (srv != SECSuccess) {
      encounteredFailure = true;
      savedErrorCode = PR_GetError();
    }
  }

  if (encounteredFailure) {
    return GetXPCOMFromNSSError(savedErrorCode);
  }

  return NS_OK;
}

nsresult nsNSSCertificateDB::handleCACertDownload(NotNull<nsIArray*> x509Certs,
                                                  nsIInterfaceRequestor* ctx) {
  // First thing we have to do is figure out which certificate we're
  // gonna present to the user.  The CA may have sent down a list of
  // certs which may or may not be a chained list of certs.  Until
  // the day we can design some solid UI for the general case, we'll
  // code to the > 90% case.  That case is where a CA sends down a
  // list that is a hierarchy whose root is either the first or
  // the last cert.  What we're gonna do is compare the first
  // 2 entries, if the second was signed by the first, we assume
  // the root cert is the first cert and display it.  Otherwise,
  // we compare the last 2 entries, if the second to last cert was
  // signed by the last cert, then we assume the last cert is the
  // root and display it.

  uint32_t numCerts;

  x509Certs->GetLength(&numCerts);

  if (numCerts == 0) return NS_OK;  // Nothing to import, so nothing to do.

  nsCOMPtr<nsIX509Cert> certToShow;
  uint32_t selCertIndex;
  if (numCerts == 1) {
    // There's only one cert, so let's show it.
    selCertIndex = 0;
    certToShow = do_QueryElementAt(x509Certs, selCertIndex);
  } else {
    nsCOMPtr<nsIX509Cert> cert0;    // first cert
    nsCOMPtr<nsIX509Cert> cert1;    // second cert
    nsCOMPtr<nsIX509Cert> certn_2;  // second to last cert
    nsCOMPtr<nsIX509Cert> certn_1;  // last cert

    cert0 = do_QueryElementAt(x509Certs, 0);
    cert1 = do_QueryElementAt(x509Certs, 1);
    certn_2 = do_QueryElementAt(x509Certs, numCerts - 2);
    certn_1 = do_QueryElementAt(x509Certs, numCerts - 1);

    nsAutoString cert0SubjectName;
    nsAutoString cert1IssuerName;
    nsAutoString certn_2IssuerName;
    nsAutoString certn_1SubjectName;

    cert0->GetSubjectName(cert0SubjectName);
    cert1->GetIssuerName(cert1IssuerName);
    certn_2->GetIssuerName(certn_2IssuerName);
    certn_1->GetSubjectName(certn_1SubjectName);

    if (cert1IssuerName.Equals(cert0SubjectName)) {
      // In this case, the first cert in the list signed the second,
      // so the first cert is the root.  Let's display it.
      selCertIndex = 0;
      certToShow = cert0;
    } else if (certn_2IssuerName.Equals(certn_1SubjectName)) {
      // In this case the last cert has signed the second to last cert.
      // The last cert is the root, so let's display it.
      selCertIndex = numCerts - 1;
      certToShow = certn_1;
    } else {
      // It's not a chain, so let's just show the first one in the
      // downloaded list.
      selCertIndex = 0;
      certToShow = cert0;
    }
  }

  if (!certToShow) return NS_ERROR_FAILURE;

  nsCOMPtr<nsICertificateDialogs> dialogs(
      do_GetService(NS_CERTIFICATEDIALOGS_CONTRACTID));
  if (!dialogs) {
    return NS_ERROR_FAILURE;
  }

  UniqueCERTCertificate tmpCert(certToShow->GetCert());
  if (!tmpCert) {
    return NS_ERROR_FAILURE;
  }

  if (!CERT_IsCACert(tmpCert.get(), nullptr)) {
    DisplayCertificateAlert(ctx, "NotACACert", certToShow);
    return NS_ERROR_FAILURE;
  }

  if (tmpCert->isperm) {
    DisplayCertificateAlert(ctx, "CaCertExists", certToShow);
    return NS_ERROR_FAILURE;
  }

  uint32_t trustBits;
  bool allows;
  nsresult rv =
      dialogs->ConfirmDownloadCACert(ctx, certToShow, &trustBits, &allows);
  if (NS_FAILED(rv)) return rv;

  if (!allows) return NS_ERROR_NOT_AVAILABLE;

  MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("trust is %d\n", trustBits));
  UniquePORTString nickname(CERT_MakeCANickname(tmpCert.get()));

  MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
          ("Created nick \"%s\"\n", nickname.get()));

  nsNSSCertTrust trust;
  trust.SetValidCA();
  trust.AddCATrust(!!(trustBits & nsIX509CertDB::TRUSTED_SSL),
                   !!(trustBits & nsIX509CertDB::TRUSTED_EMAIL));

  UniquePK11SlotInfo slot(PK11_GetInternalKeySlot());
  SECStatus srv = PK11_ImportCert(slot.get(), tmpCert.get(), CK_INVALID_HANDLE,
                                  nickname.get(),
                                  false);  // this parameter is ignored by NSS
  if (srv != SECSuccess) {
    return MapSECStatus(srv);
  }
  srv =
      ChangeCertTrustWithPossibleAuthentication(tmpCert, trust.GetTrust(), ctx);
  if (srv != SECSuccess) {
    return MapSECStatus(srv);
  }

  // Import additional delivered certificates that can be verified.

  // build a CertList for filtering
  UniqueCERTCertList certList(CERT_NewCertList());
  if (!certList) {
    return NS_ERROR_FAILURE;
  }

  // get all remaining certs into temp store

  for (uint32_t i = 0; i < numCerts; i++) {
    if (i == selCertIndex) {
      // we already processed that one
      continue;
    }

    nsCOMPtr<nsIX509Cert> remainingCert = do_QueryElementAt(x509Certs, i);
    if (!remainingCert) {
      continue;
    }

    UniqueCERTCertificate tmpCert2(remainingCert->GetCert());
    if (!tmpCert2) {
      continue;  // Let's try to import the rest of 'em
    }

    if (CERT_AddCertToListTail(certList.get(), tmpCert2.get()) != SECSuccess) {
      continue;
    }

    (void)tmpCert2.release();
  }

  return ImportCertsIntoPermanentStorage(certList);
}

nsresult nsNSSCertificateDB::ConstructCertArrayFromUniqueCertList(
    const UniqueCERTCertList& aCertListIn,
    nsTArray<RefPtr<nsIX509Cert>>& aCertListOut) {
  if (!aCertListIn.get()) {
    return NS_ERROR_INVALID_ARG;
  }

  for (CERTCertListNode* node = CERT_LIST_HEAD(aCertListIn.get());
       !CERT_LIST_END(node, aCertListIn.get()); node = CERT_LIST_NEXT(node)) {
    RefPtr cert = MakeRefPtr<nsNSSCertificate>(node->cert);
    aCertListOut.AppendElement(std::move(cert));
  }
  return NS_OK;
}

NS_IMETHODIMP
nsNSSCertificateDB::ImportCertificates(uint8_t* data, uint32_t length,
                                       uint32_t type,
                                       nsIInterfaceRequestor* ctx) {
  // We currently only handle CA certificates.
  if (type != nsIX509Cert::CA_CERT) {
    return NS_ERROR_FAILURE;
  }

  nsTArray<nsTArray<uint8_t>> certsArray;
  nsresult rv = getCertsFromPackage(certsArray, data, length);
  if (NS_FAILED(rv)) {
    return rv;
  }

  nsCOMPtr<nsIMutableArray> array = nsArrayBase::Create();
  if (!array) {
    return NS_ERROR_FAILURE;
  }

  // Now let's create some certs to work with
  for (nsTArray<uint8_t>& certDER : certsArray) {
    nsCOMPtr<nsIX509Cert> cert =
        MakeRefPtr<nsNSSCertificate>(std::move(certDER));
    nsresult rv = array->AppendElement(cert);
    if (NS_FAILED(rv)) {
      return rv;
    }
  }

  return handleCACertDownload(WrapNotNull(array), ctx);
}

/**
 * Decodes a given array of DER-encoded certificates into temporary storage.
 *
 * @param certs
 *        Array in which the decoded certificates are stored as arrays of
 *        unsigned chars.
 * @param temporaryCerts
 *        List of decoded certificates.
 */
static nsresult ImportCertsIntoTempStorage(
    nsTArray<nsTArray<uint8_t>>& certs,
    /*out*/ const UniqueCERTCertList& temporaryCerts) {
  NS_ENSURE_ARG_POINTER(temporaryCerts);

  for (nsTArray<uint8_t>& certDER : certs) {
    CERTCertificate* certificate;
    SECItem certItem;
    certItem.len = certDER.Length();
    certItem.data = certDER.Elements();
    certificate = CERT_NewTempCertificate(CERT_GetDefaultCertDB(), &certItem,
                                          nullptr, false, true);

    UniqueCERTCertificate cert(certificate);
    if (!cert) {
      continue;
    }

    if (CERT_AddCertToListTail(temporaryCerts.get(), cert.get()) ==
        SECSuccess) {
      (void)cert.release();
    }
  }

  return NS_OK;
}

NS_IMETHODIMP
nsNSSCertificateDB::ImportEmailCertificate(uint8_t* data, uint32_t length,
                                           nsIInterfaceRequestor* ctx) {
  nsTArray<nsTArray<uint8_t>> certsArray;

  nsresult rv = getCertsFromPackage(certsArray, data, length);
  if (NS_FAILED(rv)) {
    return rv;
  }

  UniqueCERTCertList temporaryCerts(CERT_NewCertList());
  if (!temporaryCerts) {
    return NS_ERROR_FAILURE;
  }

  rv = ImportCertsIntoTempStorage(certsArray, temporaryCerts);
  if (NS_FAILED(rv)) {
    return rv;
  }

  return ImportCertsIntoPermanentStorage(temporaryCerts);
}

nsresult nsNSSCertificateDB::ImportCACerts(nsTArray<nsTArray<uint8_t>>& caCerts,
                                           nsIInterfaceRequestor* ctx) {
  UniqueCERTCertList temporaryCerts(CERT_NewCertList());
  if (!temporaryCerts) {
    return NS_ERROR_FAILURE;
  }

  nsresult rv = ImportCertsIntoTempStorage(caCerts, temporaryCerts);
  if (NS_FAILED(rv)) {
    return rv;
  }

  return ImportCertsIntoPermanentStorage(temporaryCerts);
}

void nsNSSCertificateDB::DisplayCertificateAlert(nsIInterfaceRequestor* ctx,
                                                 const char* stringID,
                                                 nsIX509Cert* certToShow) {
  if (!NS_IsMainThread()) {
    NS_ERROR(
        "nsNSSCertificateDB::DisplayCertificateAlert called off the main "
        "thread");
    return;
  }

  // This shall be replaced by embedding ovverridable prompts
  // as discussed in bug 310446, and should make use of certToShow.

  nsAutoString tmpMessage;
  GetPIPNSSBundleString(stringID, tmpMessage);
  nsCOMPtr<nsIPrompt> prompt(do_GetInterface(ctx));
  if (!prompt) {
    if (NS_FAILED(nsNSSComponent::GetNewPrompter(getter_AddRefs(prompt))) ||
        !prompt) {
      return;
    }
  }

  prompt->Alert(nullptr, tmpMessage.get());
}

NS_IMETHODIMP
nsNSSCertificateDB::ImportUserCertificate(uint8_t* data, uint32_t length,
                                          nsIInterfaceRequestor* ctx) {
  if (!NS_IsMainThread()) {
    NS_ERROR(
        "nsNSSCertificateDB::ImportUserCertificate called off the main thread");
    return NS_ERROR_NOT_SAME_THREAD;
  }

  nsTArray<nsTArray<uint8_t>> certsArray;

  nsresult rv = getCertsFromPackage(certsArray, data, length);
  if (NS_FAILED(rv)) {
    return rv;
  }

  SECItem certItem;

  if (certsArray.IsEmpty()) {
    return NS_OK;
  }

  certItem.len = certsArray.ElementAt(0).Length();
  certItem.data = certsArray.ElementAt(0).Elements();

  UniqueCERTCertificate cert(CERT_NewTempCertificate(
      CERT_GetDefaultCertDB(), &certItem, nullptr, false, true));
  if (!cert) {
    return NS_ERROR_FAILURE;
  }

  UniquePK11SlotInfo slot(PK11_KeyForCertExists(cert.get(), nullptr, ctx));
  if (!slot) {
    RefPtr certToShow = MakeRefPtr<nsNSSCertificate>(cert.get());
    DisplayCertificateAlert(ctx, "UserCertIgnoredNoPrivateKey", certToShow);
    return NS_ERROR_FAILURE;
  }
  slot = nullptr;

  /* pick a nickname for the cert */
  nsAutoCString nickname;
  if (cert->nickname) {
    nickname = cert->nickname;
  } else {
    get_default_nickname(cert.get(), ctx, nickname);
  }

  /* user wants to import the cert */
  slot.reset(PK11_ImportCertForKey(cert.get(), nickname.get(), ctx));
  if (!slot) {
    return NS_ERROR_FAILURE;
  }
  slot = nullptr;

  {
    RefPtr certToShow = MakeRefPtr<nsNSSCertificate>(cert.get());
    DisplayCertificateAlert(ctx, "UserCertImported", certToShow);
  }

  rv = NS_OK;
  if (!certsArray.IsEmpty()) {
    certsArray.RemoveElementAt(0);
    rv = ImportCACerts(certsArray, ctx);
  }

  nsCOMPtr<nsIObserverService> observerService =
      mozilla::services::GetObserverService();
  if (observerService) {
    observerService->NotifyObservers(nullptr, "psm:user-certificate-added",
                                     nullptr);
  }

  return rv;
}

NS_IMETHODIMP
nsNSSCertificateDB::DeleteCertificate(nsIX509Cert* aCert) {
  NS_ENSURE_ARG_POINTER(aCert);
  UniqueCERTCertificate cert(aCert->GetCert());
  if (!cert) {
    return NS_ERROR_FAILURE;
  }

  // Temporary certificates aren't on a slot and will go away when the
  // nsIX509Cert is destructed.
  if (cert->slot) {
    uint32_t certType;
    nsresult rv = aCert->GetCertType(&certType);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
    if (certType == nsIX509Cert::USER_CERT) {
      SECStatus srv = PK11_Authenticate(cert->slot, true, nullptr);
      if (srv != SECSuccess) {
        return NS_ERROR_FAILURE;
      }
      srv = PK11_DeleteTokenCertAndKey(cert.get(), nullptr);
      if (srv != SECSuccess) {
        return NS_ERROR_FAILURE;
      }
    } else {
      // For certificates that can't be deleted (e.g. built-in roots), un-set
      // all trust bits.
      nsNSSCertTrust trust(0, 0);
      SECStatus srv = ChangeCertTrustWithPossibleAuthentication(
          cert, trust.GetTrust(), nullptr);
      if (srv != SECSuccess) {
        return NS_ERROR_FAILURE;
      }
      if (!PK11_IsReadOnly(cert->slot)) {
        srv = SEC_DeletePermCertificate(cert.get());
        if (srv != SECSuccess) {
          return NS_ERROR_FAILURE;
        }
      }
    }
  }

  nsCOMPtr<nsIObserverService> observerService =
      mozilla::services::GetObserverService();
  if (observerService) {
    observerService->NotifyObservers(nullptr, "psm:user-certificate-deleted",
                                     nullptr);
  }

  return NS_OK;
}

NS_IMETHODIMP
nsNSSCertificateDB::SetCertTrust(nsIX509Cert* cert, uint32_t type,
                                 uint32_t trusted) {
  NS_ENSURE_ARG_POINTER(cert);
  nsNSSCertTrust trust;
  switch (type) {
    case nsIX509Cert::CA_CERT:
      trust.SetValidCA();
      trust.AddCATrust(!!(trusted & nsIX509CertDB::TRUSTED_SSL),
                       !!(trusted & nsIX509CertDB::TRUSTED_EMAIL));
      break;
    case nsIX509Cert::SERVER_CERT:
      trust.SetValidPeer();
      trust.AddPeerTrust(trusted & nsIX509CertDB::TRUSTED_SSL, false);
      break;
    case nsIX509Cert::EMAIL_CERT:
      trust.SetValidPeer();
      trust.AddPeerTrust(false, !!(trusted & nsIX509CertDB::TRUSTED_EMAIL));
      break;
    default:
      // Ignore any other type of certificate (including invalid types).
      return NS_OK;
  }

  UniqueCERTCertificate nsscert(cert->GetCert());
  SECStatus srv = ChangeCertTrustWithPossibleAuthentication(
      nsscert, trust.GetTrust(), nullptr);
  return MapSECStatus(srv);
}

NS_IMETHODIMP
nsNSSCertificateDB::IsCertTrusted(nsIX509Cert* cert, uint32_t certType,
                                  uint32_t trustType, bool* _isTrusted) {
  NS_ENSURE_ARG_POINTER(_isTrusted);
  *_isTrusted = false;

  nsresult rv = BlockUntilLoadableCertsLoaded();
  if (NS_FAILED(rv)) {
    return rv;
  }

  SECStatus srv;
  UniqueCERTCertificate nsscert(cert->GetCert());
  CERTCertTrust nsstrust;
  srv = CERT_GetCertTrust(nsscert.get(), &nsstrust);
  if (srv != SECSuccess) {
    // CERT_GetCertTrust returns SECFailure if given a temporary cert that
    // doesn't have any trust information yet. This isn't an error.
    return NS_OK;
  }

  nsNSSCertTrust trust(&nsstrust);
  if (certType == nsIX509Cert::CA_CERT) {
    if (trustType & nsIX509CertDB::TRUSTED_SSL) {
      *_isTrusted = trust.HasTrustedCA(true, false);
    } else if (trustType & nsIX509CertDB::TRUSTED_EMAIL) {
      *_isTrusted = trust.HasTrustedCA(false, true);
    } else {
      return NS_ERROR_FAILURE;
    }
  } else if (certType == nsIX509Cert::SERVER_CERT) {
    if (trustType & nsIX509CertDB::TRUSTED_SSL) {
      *_isTrusted = trust.HasTrustedPeer(true, false);
    } else if (trustType & nsIX509CertDB::TRUSTED_EMAIL) {
      *_isTrusted = trust.HasTrustedPeer(false, true);
    } else {
      return NS_ERROR_FAILURE;
    }
  } else if (certType == nsIX509Cert::EMAIL_CERT) {
    if (trustType & nsIX509CertDB::TRUSTED_SSL) {
      *_isTrusted = trust.HasTrustedPeer(true, false);
    } else if (trustType & nsIX509CertDB::TRUSTED_EMAIL) {
      *_isTrusted = trust.HasTrustedPeer(false, true);
    } else {
      return NS_ERROR_FAILURE;
    }
  } /* user: ignore */
  return NS_OK;
}

NS_IMETHODIMP
nsNSSCertificateDB::ImportCertsFromFile(nsIFile* aFile, uint32_t aType) {
  NS_ENSURE_ARG(aFile);
  switch (aType) {
    case nsIX509Cert::CA_CERT:
    case nsIX509Cert::EMAIL_CERT:
      // good
      break;

    default:
      // not supported (yet)
      return NS_ERROR_FAILURE;
  }

  PRFileDesc* fd = nullptr;
  nsresult rv = aFile->OpenNSPRFileDesc(PR_RDONLY, 0, &fd);
  if (NS_FAILED(rv)) {
    return rv;
  }
  if (!fd) {
    return NS_ERROR_FAILURE;
  }

  PRFileInfo fileInfo;
  if (PR_GetOpenFileInfo(fd, &fileInfo) != PR_SUCCESS) {
    return NS_ERROR_FAILURE;
  }

  auto buf = MakeUnique<unsigned char[]>(fileInfo.size);
  int32_t bytesObtained = PR_Read(fd, buf.get(), fileInfo.size);
  PR_Close(fd);

  if (bytesObtained != fileInfo.size) {
    return NS_ERROR_FAILURE;
  }

  switch (aType) {
    case nsIX509Cert::CA_CERT:
      return ImportCertificates(buf.get(), bytesObtained, aType, nullptr);
    case nsIX509Cert::EMAIL_CERT:
      return ImportEmailCertificate(buf.get(), bytesObtained, nullptr);
    default:
      MOZ_ASSERT(false, "Unsupported type should have been filtered out");
      break;
  }

  return NS_ERROR_FAILURE;
}

NS_IMETHODIMP
nsNSSCertificateDB::ImportPKCS12File(nsIFile* aFile, const nsAString& aPassword,
                                     uint32_t* aError) {
  if (!NS_IsMainThread()) {
    return NS_ERROR_NOT_SAME_THREAD;
  }
  nsresult rv = BlockUntilLoadableCertsLoaded();
  if (NS_FAILED(rv)) {
    return rv;
  }

  NS_ENSURE_ARG(aFile);
  nsPKCS12Blob blob;
  rv = blob.ImportFromFile(aFile, aPassword, *aError);
  nsCOMPtr<nsIObserverService> observerService =
      mozilla::services::GetObserverService();
  if (NS_SUCCEEDED(rv) && observerService) {
    observerService->NotifyObservers(nullptr, "psm:user-certificate-added",
                                     nullptr);
  }

  return rv;
}

NS_IMETHODIMP
nsNSSCertificateDB::ExportPKCS12File(
    nsIFile* aFile, const nsTArray<RefPtr<nsIX509Cert>>& aCerts,
    const nsAString& aPassword, uint32_t* aError) {
  if (!NS_IsMainThread()) {
    return NS_ERROR_NOT_SAME_THREAD;
  }
  nsresult rv = BlockUntilLoadableCertsLoaded();
  if (NS_FAILED(rv)) {
    return rv;
  }

  NS_ENSURE_ARG(aFile);
  if (aCerts.IsEmpty()) {
    return NS_OK;
  }
  nsPKCS12Blob blob;
  return blob.ExportToFile(aFile, aCerts, aPassword, *aError);
}

NS_IMETHODIMP
nsNSSCertificateDB::ConstructX509FromBase64(const nsACString& base64,
                                            /*out*/ nsIX509Cert** _retval) {
  if (!_retval) {
    return NS_ERROR_INVALID_POINTER;
  }

  // Base64Decode() doesn't consider a zero length input as an error, and just
  // returns the empty string. We don't want this behavior, so the below check
  // catches this case.
  if (base64.Length() < 1) {
    return NS_ERROR_ILLEGAL_VALUE;
  }

  nsAutoCString certDER;
  nsresult rv = Base64Decode(base64, certDER);
  if (NS_FAILED(rv)) {
    return rv;
  }

  return ConstructX509FromSpan(AsBytes(Span(certDER)), _retval);
}

NS_IMETHODIMP
nsNSSCertificateDB::ConstructX509(const nsTArray<uint8_t>& certDER,
                                  nsIX509Cert** _retval) {
  return ConstructX509FromSpan(Span(certDER.Elements(), certDER.Length()),
                               _retval);
}

nsresult nsNSSCertificateDB::ConstructX509FromSpan(
    Span<const uint8_t> aInputSpan, nsIX509Cert** _retval) {
  if (NS_WARN_IF(!_retval)) {
    return NS_ERROR_INVALID_POINTER;
  }

  if (aInputSpan.Length() > std::numeric_limits<unsigned int>::max()) {
    return NS_ERROR_ILLEGAL_VALUE;
  }

  SECItem certData;
  certData.type = siDERCertBuffer;
  certData.data = const_cast<unsigned char*>(
      reinterpret_cast<const unsigned char*>(aInputSpan.Elements()));
  certData.len = aInputSpan.Length();

  UniqueCERTCertificate cert(CERT_NewTempCertificate(
      CERT_GetDefaultCertDB(), &certData, nullptr, false, true));
  if (!cert)
    return (PORT_GetError() == SEC_ERROR_NO_MEMORY) ? NS_ERROR_OUT_OF_MEMORY
                                                    : NS_ERROR_FAILURE;

  RefPtr nssCert = MakeRefPtr<nsNSSCertificate>(cert.get());
  nssCert.forget(_retval);
  return NS_OK;
}

void nsNSSCertificateDB::get_default_nickname(CERTCertificate* cert,
                                              nsIInterfaceRequestor* ctx,
                                              nsCString& nickname) {
  nickname.Truncate();

  CK_OBJECT_HANDLE keyHandle;

  if (NS_FAILED(BlockUntilLoadableCertsLoaded())) {
    return;
  }

  CERTCertDBHandle* defaultcertdb = CERT_GetDefaultCertDB();
  nsAutoCString username;
  UniquePORTString tempCN(CERT_GetCommonName(&cert->subject));
  if (tempCN) {
    username = tempCN.get();
  }

  nsAutoCString caname;
  UniquePORTString tempIssuerOrg(CERT_GetOrgName(&cert->issuer));
  if (tempIssuerOrg) {
    caname = tempIssuerOrg.get();
  }

  nsAutoString tmpNickFmt;
  GetPIPNSSBundleString("nick_template", tmpNickFmt);
  NS_ConvertUTF16toUTF8 nickFmt(tmpNickFmt);

  nsAutoCString baseName;
  baseName.AppendPrintf(nickFmt.get(), username.get(), caname.get());
  if (baseName.IsEmpty()) {
    return;
  }

  nickname = baseName;

  /*
   * We need to see if the private key exists on a token, if it does
   * then we need to check for nicknames that already exist on the smart
   * card.
   */
  UniquePK11SlotInfo slot(PK11_KeyForCertExists(cert, &keyHandle, ctx));
  if (!slot) return;

  if (!PK11_IsInternal(slot.get())) {
    nsAutoCString tmp;
    tmp.AppendPrintf("%s:%s", PK11_GetTokenName(slot.get()), baseName.get());
    if (tmp.IsEmpty()) {
      nickname.Truncate();
      return;
    }
    baseName = tmp;
    nickname = baseName;
  }

  int count = 1;
  while (true) {
    if (count > 1) {
      nsAutoCString tmp;
      tmp.AppendPrintf("%s #%d", baseName.get(), count);
      if (tmp.IsEmpty()) {
        nickname.Truncate();
        return;
      }
      nickname = tmp;
    }

    UniqueCERTCertificate dummycert;

    if (PK11_IsInternal(slot.get())) {
      /* look up the nickname to make sure it isn't in use already */
      dummycert.reset(CERT_FindCertByNickname(defaultcertdb, nickname.get()));
    } else {
      // Check the cert against others that already live on the smart card.
      dummycert.reset(PK11_FindCertFromNickname(nickname.get(), ctx));
      if (dummycert) {
        // Make sure the subject names are different.
        if (CERT_CompareName(&cert->subject, &dummycert->subject) == SECEqual) {
          /*
           * There is another certificate with the same nickname and
           * the same subject name on the smart card, so let's use this
           * nickname.
           */
          dummycert = nullptr;
        }
      }
    }
    if (!dummycert) {
      break;
    }
    count++;
  }
}

NS_IMETHODIMP
nsNSSCertificateDB::AddCertFromBase64(const nsACString& aBase64,
                                      const nsACString& aTrust,
                                      nsIX509Cert** addedCertificate) {
  // Base64Decode() doesn't consider a zero length input as an error, and just
  // returns the empty string. We don't want this behavior, so the below check
  // catches this case.
  if (aBase64.Length() < 1) {
    return NS_ERROR_ILLEGAL_VALUE;
  }

  nsAutoCString aCertDER;
  nsresult rv = Base64Decode(aBase64, aCertDER);
  if (NS_FAILED(rv)) {
    return rv;
  }
  return AddCert(aCertDER, aTrust, addedCertificate);
}

NS_IMETHODIMP
nsNSSCertificateDB::AddCert(const nsACString& aCertDER,
                            const nsACString& aTrust,
                            nsIX509Cert** addedCertificate) {
  MOZ_ASSERT(addedCertificate);
  if (!addedCertificate) {
    return NS_ERROR_INVALID_ARG;
  }
  *addedCertificate = nullptr;

  nsNSSCertTrust trust;
  if (CERT_DecodeTrustString(&trust.GetTrust(),
                             PromiseFlatCString(aTrust).get()) != SECSuccess) {
    return NS_ERROR_FAILURE;
  }

  nsCOMPtr<nsIX509Cert> newCert;
  nsresult rv =
      ConstructX509FromSpan(AsBytes(Span(aCertDER)), getter_AddRefs(newCert));
  if (NS_FAILED(rv)) {
    return rv;
  }

  UniqueCERTCertificate tmpCert(newCert->GetCert());
  if (!tmpCert) {
    return NS_ERROR_FAILURE;
  }

  // If there's already a certificate that matches this one in the database, we
  // still want to set its trust to the given value.
  if (tmpCert->isperm) {
    rv = SetCertTrustFromString(newCert, aTrust);
    if (NS_FAILED(rv)) {
      return rv;
    }
    newCert.forget(addedCertificate);
    return NS_OK;
  }

  UniquePORTString nickname(CERT_MakeCANickname(tmpCert.get()));

  MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
          ("Created nick \"%s\"\n", nickname.get()));

  UniquePK11SlotInfo slot(PK11_GetInternalKeySlot());
  SECStatus srv = PK11_ImportCert(slot.get(), tmpCert.get(), CK_INVALID_HANDLE,
                                  nickname.get(),
                                  false);  // this parameter is ignored by NSS
  if (srv != SECSuccess) {
    return MapSECStatus(srv);
  }
  srv = ChangeCertTrustWithPossibleAuthentication(tmpCert, trust.GetTrust(),
                                                  nullptr);
  if (srv != SECSuccess) {
    return MapSECStatus(srv);
  }
  newCert.forget(addedCertificate);
  return NS_OK;
}

NS_IMETHODIMP
nsNSSCertificateDB::SetCertTrustFromString(nsIX509Cert* cert,
                                           const nsACString& trustString) {
  NS_ENSURE_ARG(cert);

  CERTCertTrust trust;
  SECStatus srv =
      CERT_DecodeTrustString(&trust, PromiseFlatCString(trustString).get());
  if (srv != SECSuccess) {
    return MapSECStatus(srv);
  }
  UniqueCERTCertificate nssCert(cert->GetCert());

  srv = ChangeCertTrustWithPossibleAuthentication(nssCert, trust, nullptr);
  return MapSECStatus(srv);
}

NS_IMETHODIMP nsNSSCertificateDB::AsPKCS7Blob(
    const nsTArray<RefPtr<nsIX509Cert>>& certList, nsACString& _retval) {
  if (certList.IsEmpty()) {
    return NS_ERROR_INVALID_ARG;
  }

  UniqueNSSCMSMessage cmsg(NSS_CMSMessage_Create(nullptr));
  if (!cmsg) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
            ("nsNSSCertificateDB::AsPKCS7Blob - can't create CMS message"));
    return NS_ERROR_OUT_OF_MEMORY;
  }

  UniqueNSSCMSSignedData sigd(nullptr);
  for (const auto& cert : certList) {
    // We need an owning handle when calling nsIX509Cert::GetCert().
    UniqueCERTCertificate nssCert(cert->GetCert());
    if (!sigd) {
      sigd.reset(
          NSS_CMSSignedData_CreateCertsOnly(cmsg.get(), nssCert.get(), false));
      if (!sigd) {
        MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
                ("nsNSSCertificateDB::AsPKCS7Blob - can't create SignedData"));
        return NS_ERROR_FAILURE;
      }
    } else if (NSS_CMSSignedData_AddCertificate(sigd.get(), nssCert.get()) !=
               SECSuccess) {
      MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
              ("nsNSSCertificateDB::AsPKCS7Blob - can't add cert"));
      return NS_ERROR_FAILURE;
    }
  }

  NSSCMSContentInfo* cinfo = NSS_CMSMessage_GetContentInfo(cmsg.get());
  if (NSS_CMSContentInfo_SetContent_SignedData(cmsg.get(), cinfo, sigd.get()) !=
      SECSuccess) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
            ("nsNSSCertificateDB::AsPKCS7Blob - can't attach SignedData"));
    return NS_ERROR_FAILURE;
  }
  // cmsg owns sigd now.
  (void)sigd.release();

  UniquePLArenaPool arena(PORT_NewArena(1024));
  if (!arena) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
            ("nsNSSCertificateDB::AsPKCS7Blob - out of memory"));
    return NS_ERROR_OUT_OF_MEMORY;
  }

  SECItem certP7 = {siBuffer, nullptr, 0};
  NSSCMSEncoderContext* ecx = NSS_CMSEncoder_Start(
      cmsg.get(), nullptr, nullptr, &certP7, arena.get(), nullptr, nullptr,
      nullptr, nullptr, nullptr, nullptr);
  if (!ecx) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
            ("nsNSSCertificateDB::AsPKCS7Blob - can't create encoder"));
    return NS_ERROR_FAILURE;
  }

  if (NSS_CMSEncoder_Finish(ecx) != SECSuccess) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
            ("nsNSSCertificateDB::AsPKCS7Blob - failed to add encoded data"));
    return NS_ERROR_FAILURE;
  }

  _retval.Assign(nsDependentCSubstring(
      reinterpret_cast<const char*>(certP7.data), certP7.len));
  return NS_OK;
}

NS_IMETHODIMP
nsNSSCertificateDB::GetCerts(nsTArray<RefPtr<nsIX509Cert>>& _retval) {
  nsresult rv = BlockUntilLoadableCertsLoaded();
  if (NS_FAILED(rv)) {
    return rv;
  }

  rv = CheckForSmartCardChanges();
  if (NS_FAILED(rv)) {
    return rv;
  }

  AutoSearchingForClientAuthCertificates _;
  UniqueCERTCertList certList(PK11_ListCerts(PK11CertListUnique, nullptr));
  if (!certList) {
    return NS_ERROR_FAILURE;
  }
  return nsNSSCertificateDB::ConstructCertArrayFromUniqueCertList(certList,
                                                                  _retval);
}

static mozilla::Result<VerifyUsage, nsresult> MapX509UsageToVerifierUsage(
    nsIX509CertDB::VerifyUsage usage) {
  switch (usage) {
    case nsIX509CertDB::verifyUsageTLSServer:
      return VerifyUsage::TLSServer;
    case nsIX509CertDB::verifyUsageTLSServerCA:
      return VerifyUsage::TLSServerCA;
    case nsIX509CertDB::verifyUsageTLSClient:
      return VerifyUsage::TLSClient;
    case nsIX509CertDB::verifyUsageTLSClientCA:
      return VerifyUsage::TLSClientCA;
    case nsIX509CertDB::verifyUsageEmailSigner:
      return VerifyUsage::EmailSigner;
    case nsIX509CertDB::verifyUsageEmailRecipient:
      return VerifyUsage::EmailRecipient;
    case nsIX509CertDB::verifyUsageEmailCA:
      return VerifyUsage::EmailCA;
  }
  return Err(NS_ERROR_INVALID_ARG);
}

nsresult VerifyCertAtTime(nsIX509Cert* aCert, nsIX509CertDB::VerifyUsage aUsage,
                          uint32_t aFlags, const nsACString& aHostname,
                          mozilla::pkix::Time aTime,
                          const Maybe<nsTArray<uint8_t>>& aSctsFromTls,
                          nsTArray<RefPtr<nsIX509Cert>>& aVerifiedChain,
                          bool* aHasEVPolicy,
                          int32_t* /*PRErrorCode*/ _retval) {
  NS_ENSURE_ARG_POINTER(aCert);
  NS_ENSURE_ARG_POINTER(aHasEVPolicy);
  NS_ENSURE_ARG_POINTER(_retval);

  if (!aVerifiedChain.IsEmpty()) {
    return NS_ERROR_INVALID_ARG;
  }

  *aHasEVPolicy = false;
  *_retval = PR_UNKNOWN_ERROR;

  RefPtr<SharedCertVerifier> certVerifier(GetDefaultCertVerifier());
  NS_ENSURE_TRUE(certVerifier, NS_ERROR_FAILURE);

  nsTArray<nsTArray<uint8_t>> resultChain;
  EVStatus evStatus;
  mozilla::pkix::Result result;

  nsTArray<uint8_t> certBytes;
  nsresult nsrv = aCert->GetRawDER(certBytes);
  if (NS_FAILED(nsrv)) {
    return nsrv;
  }

  if (!aHostname.IsVoid() && aUsage == nsIX509CertDB::verifyUsageTLSServer) {
    result =
        certVerifier->VerifySSLServerCert(certBytes, aTime,
                                          nullptr,  // Assume no context
                                          aHostname, resultChain, aFlags,
                                          Nothing(),     // extraCertificates
                                          Nothing(),     // stapledOCSPResponse
                                          aSctsFromTls,  // sctsFromTLSExtension
                                          Nothing(),     // dcInfo
                                          OriginAttributes(), &evStatus);
  } else {
    const nsCString& flatHostname = PromiseFlatCString(aHostname);
    VerifyUsage vu = MOZ_TRY(MapX509UsageToVerifierUsage(aUsage));
    result = certVerifier->VerifyCert(
        certBytes, vu, aTime,
        nullptr,  // Assume no context
        aHostname.IsVoid() ? nullptr : flatHostname.get(), resultChain, aFlags,
        Nothing(),     // extraCertificates
        Nothing(),     // stapledOCSPResponse
        aSctsFromTls,  // sctsFromTLSExtension
        OriginAttributes(), &evStatus);
  }

  if (result == mozilla::pkix::Success) {
    for (auto& certDER : resultChain) {
      RefPtr cert = MakeRefPtr<nsNSSCertificate>(std::move(certDER));
      aVerifiedChain.AppendElement(std::move(cert));
    }

    if (evStatus == EVStatus::EV) {
      *aHasEVPolicy = true;
    }
  }

  *_retval = mozilla::pkix::MapResultToPRErrorCode(result);

  return NS_OK;
}

class VerifyCertAtTimeTask final : public CryptoTask {
 public:
  VerifyCertAtTimeTask(nsIX509Cert* aCert, nsIX509CertDB::VerifyUsage aUsage,
                       uint32_t aFlags, const nsACString& aHostname,
                       uint64_t aTime, const nsTArray<uint8_t>& aSctsFromTls,
                       nsICertVerificationCallback* aCallback)
      : mCert(aCert),
        mUsage(aUsage),
        mFlags(aFlags),
        mHostname(aHostname),
        mTime(aTime),
        mCallback(new nsMainThreadPtrHolder<nsICertVerificationCallback>(
            "nsICertVerificationCallback", aCallback)),
        mPRErrorCode(SEC_ERROR_LIBRARY_FAILURE),
        mHasEVPolicy(false) {
    if (aSctsFromTls.Length() > 0) {
      mSctsFromTls.emplace(aSctsFromTls.Clone());
    }
  }

 private:
  virtual nsresult CalculateResult() override {
    nsCOMPtr<nsIX509CertDB> certDB = do_GetService(NS_X509CERTDB_CONTRACTID);
    if (!certDB) {
      return NS_ERROR_FAILURE;
    }
    return VerifyCertAtTime(mCert, mUsage, mFlags, mHostname,
                            mozilla::pkix::TimeFromEpochInSeconds(mTime),
                            mSctsFromTls, mVerifiedCertList, &mHasEVPolicy,
                            &mPRErrorCode);
  }

  virtual void CallCallback(nsresult rv) override {
    if (NS_FAILED(rv)) {
      nsTArray<RefPtr<nsIX509Cert>> tmp;
      (void)mCallback->VerifyCertFinished(SEC_ERROR_LIBRARY_FAILURE, tmp,
                                          false);
    } else {
      (void)mCallback->VerifyCertFinished(mPRErrorCode, mVerifiedCertList,
                                          mHasEVPolicy);
    }
  }

  nsCOMPtr<nsIX509Cert> mCert;
  nsIX509CertDB::VerifyUsage mUsage;
  uint32_t mFlags;
  nsCString mHostname;
  uint64_t mTime;
  nsMainThreadPtrHandle<nsICertVerificationCallback> mCallback;
  int32_t mPRErrorCode;
  nsTArray<RefPtr<nsIX509Cert>> mVerifiedCertList;
  bool mHasEVPolicy;
  Maybe<nsTArray<uint8_t>> mSctsFromTls;
};

NS_IMETHODIMP
nsNSSCertificateDB::AsyncVerifyCertAtTime(
    nsIX509Cert* aCert, nsIX509CertDB::VerifyUsage aUsage, uint32_t aFlags,
    const nsACString& aHostname, uint64_t aTime,
    const nsTArray<uint8_t>& aSctsFromTls,
    nsICertVerificationCallback* aCallback) {
  RefPtr<VerifyCertAtTimeTask> task(new VerifyCertAtTimeTask(
      aCert, aUsage, aFlags, aHostname, aTime, aSctsFromTls, aCallback));
  return task->Dispatch();
}

NS_IMETHODIMP
nsNSSCertificateDB::ClearOCSPCache() {
  RefPtr<SharedCertVerifier> certVerifier(GetDefaultCertVerifier());
  NS_ENSURE_TRUE(certVerifier, NS_ERROR_FAILURE);
  certVerifier->ClearOCSPCache();
  return NS_OK;
}

NS_IMETHODIMP
nsNSSCertificateDB::GetAndroidCertificateFromAlias(
    const nsAString& aAlias, /*out*/ nsIX509Cert** aResult) {
  *aResult = nullptr;
#ifndef MOZ_WIDGET_ANDROID
  return NS_ERROR_NOT_AVAILABLE;
#else
  if (!jni::IsAvailable()) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("JNI not available"));
    return NS_ERROR_NOT_AVAILABLE;
  }

  jni::String::LocalRef alias = jni::StringParam(aAlias);
  jni::ByteArray::LocalRef certificateBytes =
      java::ClientAuthCertificateManager::GetCertificateFromAlias(alias);
  if (!certificateBytes) {
    return NS_ERROR_NOT_AVAILABLE;
  }
  nsTArray<uint8_t> certificateByteArray(
      certificateBytes->GetElements().Elements(), certificateBytes->Length());
  nsCOMPtr<nsIX509Cert> certificate(
      new nsNSSCertificate(std::move(certificateByteArray)));
  certificate.forget(aResult);
  return NS_OK;
#endif  // MOZ_WIDGET_ANDROID
}
