summaryrefslogtreecommitdiffstats
path: root/security/manager/ssl/nsNSSCertificateDB.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'security/manager/ssl/nsNSSCertificateDB.cpp')
-rw-r--r--security/manager/ssl/nsNSSCertificateDB.cpp1658
1 files changed, 1658 insertions, 0 deletions
diff --git a/security/manager/ssl/nsNSSCertificateDB.cpp b/security/manager/ssl/nsNSSCertificateDB.cpp
new file mode 100644
index 000000000..3d7c7eec4
--- /dev/null
+++ b/security/manager/ssl/nsNSSCertificateDB.cpp
@@ -0,0 +1,1658 @@
+/* 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 "SharedSSLState.h"
+#include "certdb.h"
+#include "mozilla/Base64.h"
+#include "mozilla/Casting.h"
+#include "mozilla/Unused.h"
+#include "nsArray.h"
+#include "nsArrayUtils.h"
+#include "nsCOMPtr.h"
+#include "nsCRT.h"
+#include "nsComponentManagerUtils.h"
+#include "nsICertificateDialogs.h"
+#include "nsIFile.h"
+#include "nsIMutableArray.h"
+#include "nsIObserverService.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsIPrompt.h"
+#include "nsNSSCertHelper.h"
+#include "nsNSSCertTrust.h"
+#include "nsNSSCertificate.h"
+#include "nsNSSComponent.h"
+#include "nsNSSHelper.h"
+#include "nsNSSShutDown.h"
+#include "nsPK11TokenDB.h"
+#include "nsPKCS12Blob.h"
+#include "nsPromiseFlatString.h"
+#include "nsProxyRelease.h"
+#include "nsReadableUtils.h"
+#include "nsThreadUtils.h"
+#include "nspr.h"
+#include "pkix/Time.h"
+#include "pkix/pkixnss.h"
+#include "pkix/pkixtypes.h"
+#include "secasn1.h"
+#include "secder.h"
+#include "secerr.h"
+#include "ssl.h"
+
+#ifdef XP_WIN
+#include <winsock.h> // for ntohl
+#endif
+
+using namespace mozilla;
+using namespace mozilla::psm;
+using mozilla::psm::SharedSSLState;
+
+extern LazyLogModule gPIPNSSLog;
+
+static nsresult
+attemptToLogInWithDefaultPassword()
+{
+#ifdef NSS_DISABLE_DBM
+ // The SQL NSS DB requires the user to be authenticated to set certificate
+ // trust settings, even if the user's password is empty. To maintain
+ // compatibility with the DBM-based database, try to log in with the
+ // default empty password. This will allow, at least, tests that need to
+ // change certificate trust to pass on all platforms. TODO(bug 978120): Do
+ // proper testing and/or implement a better solution so that we are confident
+ // that this does the correct thing outside of xpcshell tests too.
+ UniquePK11SlotInfo slot(PK11_GetInternalKeySlot());
+ if (!slot) {
+ return MapSECStatus(SECFailure);
+ }
+ if (PK11_NeedUserInit(slot.get())) {
+ // Ignore the return value. Presumably PK11_InitPin will fail if the user
+ // has a non-default password.
+ Unused << PK11_InitPin(slot.get(), nullptr, nullptr);
+ }
+#endif
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsNSSCertificateDB, nsIX509CertDB)
+
+nsNSSCertificateDB::~nsNSSCertificateDB()
+{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown()) {
+ return;
+ }
+
+ shutdown(ShutdownCalledFrom::Object);
+}
+
+NS_IMETHODIMP
+nsNSSCertificateDB::FindCertByNickname(const nsAString& nickname,
+ nsIX509Cert** _rvCert)
+{
+ NS_ENSURE_ARG_POINTER(_rvCert);
+ *_rvCert = nullptr;
+
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ char *asciiname = nullptr;
+ NS_ConvertUTF16toUTF8 aUtf8Nickname(nickname);
+ asciiname = const_cast<char*>(aUtf8Nickname.get());
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("Getting \"%s\"\n", asciiname));
+ UniqueCERTCertificate cert(PK11_FindCertFromNickname(asciiname, nullptr));
+ if (!cert) {
+ cert.reset(CERT_FindCertByNickname(CERT_GetDefaultCertDB(), asciiname));
+ }
+ if (cert) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("got it\n"));
+ nsCOMPtr<nsIX509Cert> pCert = nsNSSCertificate::Create(cert.get());
+ if (pCert) {
+ pCert.forget(_rvCert);
+ return NS_OK;
+ }
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsNSSCertificateDB::FindCertByDBKey(const char* aDBKey,nsIX509Cert** _cert)
+{
+ NS_ENSURE_ARG_POINTER(aDBKey);
+ NS_ENSURE_ARG(aDBKey[0]);
+ NS_ENSURE_ARG_POINTER(_cert);
+ *_cert = nullptr;
+
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ UniqueCERTCertificate cert;
+ nsresult 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;
+ }
+ nsCOMPtr<nsIX509Cert> nssCert = nsNSSCertificate::Create(cert.get());
+ if (!nssCert) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ nssCert.forget(_cert);
+ return NS_OK;
+}
+
+nsresult
+nsNSSCertificateDB::FindCertByDBKey(const char* 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());
+
+ cert.reset(CERT_FindCertByIssuerAndSN(CERT_GetDefaultCertDB(), &issuerSN));
+ return NS_OK;
+}
+
+SECStatus
+collect_certs(void *arg, SECItem **certs, int numcerts)
+{
+ CERTDERCerts *collectArgs;
+ SECItem *cert;
+ SECStatus rv;
+
+ collectArgs = (CERTDERCerts *)arg;
+
+ collectArgs->numcerts = numcerts;
+ collectArgs->rawCerts = (SECItem *) PORT_ArenaZAlloc(collectArgs->arena,
+ sizeof(SECItem) * numcerts);
+ if (!collectArgs->rawCerts)
+ return(SECFailure);
+
+ cert = collectArgs->rawCerts;
+
+ while ( numcerts-- ) {
+ rv = SECITEM_CopyItem(collectArgs->arena, cert, *certs);
+ if ( rv == SECFailure )
+ return(SECFailure);
+ cert++;
+ certs++;
+ }
+
+ return (SECSuccess);
+}
+
+CERTDERCerts*
+nsNSSCertificateDB::getCertsFromPackage(const UniquePLArenaPool& arena,
+ uint8_t* data, uint32_t length,
+ const nsNSSShutDownPreventionLock& /*proofOfLock*/)
+{
+ CERTDERCerts* collectArgs = PORT_ArenaZNew(arena.get(), CERTDERCerts);
+ if (!collectArgs) {
+ return nullptr;
+ }
+
+ collectArgs->arena = arena.get();
+ if (CERT_DecodeCertPackage(BitwiseCast<char*, uint8_t*>(data), length,
+ collect_certs, collectArgs) != SECSuccess) {
+ return nullptr;
+ }
+
+ return collectArgs;
+}
+
+nsresult
+nsNSSCertificateDB::handleCACertDownload(NotNull<nsIArray*> x509Certs,
+ nsIInterfaceRequestor *ctx,
+ const nsNSSShutDownPreventionLock &proofOfLock)
+{
+ // 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);
+ NS_ASSERTION(numCerts > 0, "Didn't get any certs to import.");
+ 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);
+
+ nsXPIDLString cert0SubjectName;
+ nsXPIDLString cert1IssuerName;
+ nsXPIDLString certn_2IssuerName;
+ nsXPIDLString 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;
+ nsresult rv = ::getNSSDialogs(getter_AddRefs(dialogs),
+ NS_GET_IID(nsICertificateDialogs),
+ NS_CERTIFICATEDIALOGS_CONTRACTID);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ UniqueCERTCertificate tmpCert(certToShow->GetCert());
+ if (!tmpCert) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!CERT_IsCACert(tmpCert.get(), nullptr)) {
+ DisplayCertificateAlert(ctx, "NotACACert", certToShow, proofOfLock);
+ return NS_ERROR_FAILURE;
+ }
+
+ if (tmpCert->isperm) {
+ DisplayCertificateAlert(ctx, "CaCertExists", certToShow, proofOfLock);
+ return NS_ERROR_FAILURE;
+ }
+
+ uint32_t trustBits;
+ bool allows;
+ 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),
+ !!(trustBits & nsIX509CertDB::TRUSTED_OBJSIGN));
+
+ if (CERT_AddTempCertToPerm(tmpCert.get(), nickname.get(),
+ trust.GetTrust()) != SECSuccess) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // 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;
+ }
+
+ Unused << tmpCert2.release();
+ }
+
+ return ImportValidCACertsInList(certList, ctx, proofOfLock);
+}
+
+NS_IMETHODIMP
+nsNSSCertificateDB::ImportCertificates(uint8_t* data, uint32_t length,
+ uint32_t type,
+ nsIInterfaceRequestor* ctx)
+{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // We currently only handle CA certificates.
+ if (type != nsIX509Cert::CA_CERT) {
+ return NS_ERROR_FAILURE;
+ }
+
+ UniquePLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
+ if (!arena) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ CERTDERCerts* certCollection = getCertsFromPackage(arena, data, length,
+ locker);
+ if (!certCollection) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIMutableArray> array = nsArrayBase::Create();
+ if (!array) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Now let's create some certs to work with
+ for (int i = 0; i < certCollection->numcerts; i++) {
+ SECItem* currItem = &certCollection->rawCerts[i];
+ nsCOMPtr<nsIX509Cert> cert = nsNSSCertificate::ConstructFromDER(
+ BitwiseCast<char*, unsigned char*>(currItem->data), currItem->len);
+ if (!cert) {
+ return NS_ERROR_FAILURE;
+ }
+ nsresult rv = array->AppendElement(cert, false);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ return handleCACertDownload(WrapNotNull(array), ctx, locker);
+}
+
+/**
+ * Filters an array of certs by usage and imports them into temporary storage.
+ *
+ * @param numcerts
+ * Size of the |certs| array.
+ * @param certs
+ * Pointer to array of certs to import.
+ * @param usage
+ * Usage the certs should be filtered on.
+ * @param caOnly
+ * Whether to import only CA certs.
+ * @param filteredCerts
+ * List of certs that weren't filtered out and were successfully imported.
+ */
+static nsresult
+ImportCertsIntoTempStorage(int numcerts, SECItem* certs,
+ const SECCertUsage usage, const bool caOnly,
+ const nsNSSShutDownPreventionLock& /*proofOfLock*/,
+ /*out*/ const UniqueCERTCertList& filteredCerts)
+{
+ NS_ENSURE_ARG_MIN(numcerts, 1);
+ NS_ENSURE_ARG_POINTER(certs);
+ NS_ENSURE_ARG_POINTER(filteredCerts.get());
+
+ // CERT_ImportCerts() expects an array of *pointers* to SECItems, so we have
+ // to convert |certs| to such a format first.
+ SECItem** ptrArray =
+ static_cast<SECItem**>(PORT_Alloc(sizeof(SECItem*) * numcerts));
+ if (!ptrArray) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ for (int i = 0; i < numcerts; i++) {
+ ptrArray[i] = &certs[i];
+ }
+
+ CERTCertificate** importedCerts = nullptr;
+ SECStatus srv = CERT_ImportCerts(CERT_GetDefaultCertDB(), usage,
+ numcerts, ptrArray, &importedCerts, false,
+ caOnly, nullptr);
+ PORT_Free(ptrArray);
+ ptrArray = nullptr;
+ if (srv != SECSuccess) {
+ return NS_ERROR_FAILURE;
+ }
+
+ for (int i = 0; i < numcerts; i++) {
+ if (!importedCerts[i]) {
+ continue;
+ }
+
+ UniqueCERTCertificate cert(CERT_DupCertificate(importedCerts[i]));
+ if (!cert) {
+ continue;
+ }
+
+ if (CERT_AddCertToListTail(filteredCerts.get(), cert.get()) == SECSuccess) {
+ Unused << cert.release();
+ }
+ }
+
+ CERT_DestroyCertArray(importedCerts, numcerts);
+
+ // CERT_ImportCerts() ignores its |usage| parameter, so we have to manually
+ // filter out unwanted certs.
+ if (CERT_FilterCertListByUsage(filteredCerts.get(), usage, caOnly)
+ != SECSuccess) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+static SECStatus
+ImportCertsIntoPermanentStorage(const UniqueCERTCertList& certChain,
+ const SECCertUsage usage, const bool caOnly)
+{
+ int chainLen = 0;
+ for (CERTCertListNode *chainNode = CERT_LIST_HEAD(certChain);
+ !CERT_LIST_END(chainNode, certChain);
+ chainNode = CERT_LIST_NEXT(chainNode)) {
+ chainLen++;
+ }
+
+ SECItem **rawArray;
+ rawArray = (SECItem **) PORT_Alloc(chainLen * sizeof(SECItem *));
+ if (!rawArray) {
+ return SECFailure;
+ }
+
+ int i = 0;
+ for (CERTCertListNode *chainNode = CERT_LIST_HEAD(certChain);
+ !CERT_LIST_END(chainNode, certChain);
+ chainNode = CERT_LIST_NEXT(chainNode), i++) {
+ rawArray[i] = &chainNode->cert->derCert;
+ }
+ SECStatus srv = CERT_ImportCerts(CERT_GetDefaultCertDB(), usage, chainLen,
+ rawArray, nullptr, true, caOnly, nullptr);
+
+ PORT_Free(rawArray);
+ return srv;
+}
+
+NS_IMETHODIMP
+nsNSSCertificateDB::ImportEmailCertificate(uint8_t* data, uint32_t length,
+ nsIInterfaceRequestor* ctx)
+{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ UniquePLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
+ if (!arena) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ CERTDERCerts *certCollection = getCertsFromPackage(arena, data, length, locker);
+ if (!certCollection) {
+ return NS_ERROR_FAILURE;
+ }
+
+ UniqueCERTCertList filteredCerts(CERT_NewCertList());
+ if (!filteredCerts) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv = ImportCertsIntoTempStorage(certCollection->numcerts,
+ certCollection->rawCerts,
+ certUsageEmailRecipient,
+ false, locker, filteredCerts);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ RefPtr<SharedCertVerifier> certVerifier(GetDefaultCertVerifier());
+ if (!certVerifier) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Iterate through the filtered cert list and import verified certs into
+ // permanent storage.
+ // Note: We verify the certs in order to prevent DoS attacks. See Bug 249004.
+ for (CERTCertListNode* node = CERT_LIST_HEAD(filteredCerts.get());
+ !CERT_LIST_END(node, filteredCerts.get());
+ node = CERT_LIST_NEXT(node)) {
+ if (!node->cert) {
+ continue;
+ }
+
+ UniqueCERTCertList certChain;
+ mozilla::pkix::Result result =
+ certVerifier->VerifyCert(node->cert, certificateUsageEmailRecipient,
+ mozilla::pkix::Now(), ctx, nullptr, certChain);
+ if (result != mozilla::pkix::Success) {
+ nsCOMPtr<nsIX509Cert> certToShow = nsNSSCertificate::Create(node->cert);
+ DisplayCertificateAlert(ctx, "NotImportingUnverifiedCert", certToShow, locker);
+ continue;
+ }
+ SECStatus srv = ImportCertsIntoPermanentStorage(certChain,
+ certUsageEmailRecipient,
+ false);
+ if (srv != SECSuccess) {
+ return NS_ERROR_FAILURE;
+ }
+ CERT_SaveSMimeProfile(node->cert, nullptr, nullptr);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsNSSCertificateDB::ImportValidCACerts(int numCACerts, SECItem* caCerts,
+ nsIInterfaceRequestor* ctx,
+ const nsNSSShutDownPreventionLock& proofOfLock)
+{
+ UniqueCERTCertList filteredCerts(CERT_NewCertList());
+ if (!filteredCerts) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv = ImportCertsIntoTempStorage(numCACerts, caCerts, certUsageAnyCA,
+ true, proofOfLock, filteredCerts);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return ImportValidCACertsInList(filteredCerts, ctx, proofOfLock);
+}
+
+nsresult
+nsNSSCertificateDB::ImportValidCACertsInList(const UniqueCERTCertList& filteredCerts,
+ nsIInterfaceRequestor* ctx,
+ const nsNSSShutDownPreventionLock& proofOfLock)
+{
+ RefPtr<SharedCertVerifier> certVerifier(GetDefaultCertVerifier());
+ if (!certVerifier) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Iterate through the filtered cert list and import verified certs into
+ // permanent storage.
+ // Note: We verify the certs in order to prevent DoS attacks. See Bug 249004.
+ for (CERTCertListNode* node = CERT_LIST_HEAD(filteredCerts.get());
+ !CERT_LIST_END(node, filteredCerts.get());
+ node = CERT_LIST_NEXT(node)) {
+ UniqueCERTCertList certChain;
+ mozilla::pkix::Result result =
+ certVerifier->VerifyCert(node->cert, certificateUsageVerifyCA,
+ mozilla::pkix::Now(), ctx, nullptr, certChain);
+ if (result != mozilla::pkix::Success) {
+ nsCOMPtr<nsIX509Cert> certToShow = nsNSSCertificate::Create(node->cert);
+ DisplayCertificateAlert(ctx, "NotImportingUnverifiedCert", certToShow, proofOfLock);
+ continue;
+ }
+
+ SECStatus srv = ImportCertsIntoPermanentStorage(certChain, certUsageAnyCA,
+ true);
+ if (srv != SECSuccess) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ return NS_OK;
+}
+
+void nsNSSCertificateDB::DisplayCertificateAlert(nsIInterfaceRequestor *ctx,
+ const char *stringID,
+ nsIX509Cert *certToShow,
+ const nsNSSShutDownPreventionLock &/*proofOfLock*/)
+{
+ static NS_DEFINE_CID(kNSSComponentCID, NS_NSSCOMPONENT_CID);
+
+ if (!NS_IsMainThread()) {
+ NS_ERROR("nsNSSCertificateDB::DisplayCertificateAlert called off the main thread");
+ return;
+ }
+
+ nsCOMPtr<nsIInterfaceRequestor> my_ctx = ctx;
+ if (!my_ctx) {
+ my_ctx = new PipUIContext();
+ }
+
+ // This shall be replaced by embedding ovverridable prompts
+ // as discussed in bug 310446, and should make use of certToShow.
+
+ nsresult rv;
+ nsCOMPtr<nsINSSComponent> nssComponent(do_GetService(kNSSComponentCID, &rv));
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoString tmpMessage;
+ nssComponent->GetPIPNSSBundleString(stringID, tmpMessage);
+
+ nsCOMPtr<nsIPrompt> prompt (do_GetInterface(my_ctx));
+ if (!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;
+ }
+
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ UniquePLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
+ if (!arena) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ CERTDERCerts* collectArgs = getCertsFromPackage(arena, data, length, locker);
+ if (!collectArgs) {
+ return NS_ERROR_FAILURE;
+ }
+
+ UniqueCERTCertificate cert(
+ CERT_NewTempCertificate(CERT_GetDefaultCertDB(), collectArgs->rawCerts,
+ nullptr, false, true));
+ if (!cert) {
+ return NS_ERROR_FAILURE;
+ }
+
+ UniquePK11SlotInfo slot(PK11_KeyForCertExists(cert.get(), nullptr, ctx));
+ if (!slot) {
+ nsCOMPtr<nsIX509Cert> certToShow = nsNSSCertificate::Create(cert.get());
+ DisplayCertificateAlert(ctx, "UserCertIgnoredNoPrivateKey", certToShow, locker);
+ 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, locker);
+ }
+
+ /* user wants to import the cert */
+ slot.reset(PK11_ImportCertForKey(cert.get(), nickname.get(), ctx));
+ if (!slot) {
+ return NS_ERROR_FAILURE;
+ }
+ slot = nullptr;
+
+ {
+ nsCOMPtr<nsIX509Cert> certToShow = nsNSSCertificate::Create(cert.get());
+ DisplayCertificateAlert(ctx, "UserCertImported", certToShow, locker);
+ }
+
+ int numCACerts = collectArgs->numcerts - 1;
+ if (numCACerts) {
+ SECItem* caCerts = collectArgs->rawCerts + 1;
+ return ImportValidCACerts(numCACerts, caCerts, ctx, locker);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNSSCertificateDB::DeleteCertificate(nsIX509Cert *aCert)
+{
+ NS_ENSURE_ARG_POINTER(aCert);
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ UniqueCERTCertificate cert(aCert->GetCert());
+ if (!cert) {
+ return NS_ERROR_FAILURE;
+ }
+ SECStatus srv = SECSuccess;
+
+ uint32_t certType;
+ aCert->GetCertType(&certType);
+ if (NS_FAILED(aCert->MarkForPermDeletion()))
+ {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (cert->slot && certType != nsIX509Cert::USER_CERT) {
+ // To delete a cert of a slot (builtin, most likely), mark it as
+ // completely untrusted. This way we keep a copy cached in the
+ // local database, and next time we try to load it off of the
+ // external token/slot, we'll know not to trust it. We don't
+ // want to do that with user certs, because a user may re-store
+ // the cert onto the card again at which point we *will* want to
+ // trust that cert if it chains up properly.
+ nsNSSCertTrust trust(0, 0, 0);
+ srv = CERT_ChangeCertTrust(CERT_GetDefaultCertDB(),
+ cert.get(), trust.GetTrust());
+ }
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("cert deleted: %d", srv));
+ return (srv) ? NS_ERROR_FAILURE : NS_OK;
+}
+
+NS_IMETHODIMP
+nsNSSCertificateDB::SetCertTrust(nsIX509Cert *cert,
+ uint32_t type,
+ uint32_t trusted)
+{
+ NS_ENSURE_ARG_POINTER(cert);
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ nsNSSCertTrust trust;
+ nsresult rv;
+ UniqueCERTCertificate nsscert(cert->GetCert());
+
+ rv = attemptToLogInWithDefaultPassword();
+ if (NS_WARN_IF(rv != NS_OK)) {
+ return rv;
+ }
+
+ SECStatus srv;
+ if (type == nsIX509Cert::CA_CERT) {
+ // always start with untrusted and move up
+ trust.SetValidCA();
+ trust.AddCATrust(!!(trusted & nsIX509CertDB::TRUSTED_SSL),
+ !!(trusted & nsIX509CertDB::TRUSTED_EMAIL),
+ !!(trusted & nsIX509CertDB::TRUSTED_OBJSIGN));
+ srv = CERT_ChangeCertTrust(CERT_GetDefaultCertDB(),
+ nsscert.get(),
+ trust.GetTrust());
+ } else if (type == nsIX509Cert::SERVER_CERT) {
+ // always start with untrusted and move up
+ trust.SetValidPeer();
+ trust.AddPeerTrust(trusted & nsIX509CertDB::TRUSTED_SSL, 0, 0);
+ srv = CERT_ChangeCertTrust(CERT_GetDefaultCertDB(),
+ nsscert.get(),
+ trust.GetTrust());
+ } else if (type == nsIX509Cert::EMAIL_CERT) {
+ // always start with untrusted and move up
+ trust.SetValidPeer();
+ trust.AddPeerTrust(0, !!(trusted & nsIX509CertDB::TRUSTED_EMAIL), 0);
+ srv = CERT_ChangeCertTrust(CERT_GetDefaultCertDB(),
+ nsscert.get(),
+ trust.GetTrust());
+ } else {
+ // ignore user certs
+ return NS_OK;
+ }
+ return MapSECStatus(srv);
+}
+
+NS_IMETHODIMP
+nsNSSCertificateDB::IsCertTrusted(nsIX509Cert *cert,
+ uint32_t certType,
+ uint32_t trustType,
+ bool *_isTrusted)
+{
+ NS_ENSURE_ARG_POINTER(_isTrusted);
+ *_isTrusted = false;
+
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ SECStatus srv;
+ UniqueCERTCertificate nsscert(cert->GetCert());
+ CERTCertTrust nsstrust;
+ srv = CERT_GetCertTrust(nsscert.get(), &nsstrust);
+ if (srv != SECSuccess)
+ return NS_ERROR_FAILURE;
+
+ nsNSSCertTrust trust(&nsstrust);
+ if (certType == nsIX509Cert::CA_CERT) {
+ if (trustType & nsIX509CertDB::TRUSTED_SSL) {
+ *_isTrusted = trust.HasTrustedCA(true, false, false);
+ } else if (trustType & nsIX509CertDB::TRUSTED_EMAIL) {
+ *_isTrusted = trust.HasTrustedCA(false, true, false);
+ } else if (trustType & nsIX509CertDB::TRUSTED_OBJSIGN) {
+ *_isTrusted = trust.HasTrustedCA(false, false, true);
+ } else {
+ return NS_ERROR_FAILURE;
+ }
+ } else if (certType == nsIX509Cert::SERVER_CERT) {
+ if (trustType & nsIX509CertDB::TRUSTED_SSL) {
+ *_isTrusted = trust.HasTrustedPeer(true, false, false);
+ } else if (trustType & nsIX509CertDB::TRUSTED_EMAIL) {
+ *_isTrusted = trust.HasTrustedPeer(false, true, false);
+ } else if (trustType & nsIX509CertDB::TRUSTED_OBJSIGN) {
+ *_isTrusted = trust.HasTrustedPeer(false, false, true);
+ } else {
+ return NS_ERROR_FAILURE;
+ }
+ } else if (certType == nsIX509Cert::EMAIL_CERT) {
+ if (trustType & nsIX509CertDB::TRUSTED_SSL) {
+ *_isTrusted = trust.HasTrustedPeer(true, false, false);
+ } else if (trustType & nsIX509CertDB::TRUSTED_EMAIL) {
+ *_isTrusted = trust.HasTrustedPeer(false, true, false);
+ } else if (trustType & nsIX509CertDB::TRUSTED_OBJSIGN) {
+ *_isTrusted = trust.HasTrustedPeer(false, false, true);
+ } else {
+ return NS_ERROR_FAILURE;
+ }
+ } /* user: ignore */
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNSSCertificateDB::ImportCertsFromFile(nsIFile* aFile, uint32_t aType)
+{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ 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;
+ }
+
+ nsCOMPtr<nsIInterfaceRequestor> cxt = new PipUIContext();
+
+ switch (aType) {
+ case nsIX509Cert::CA_CERT:
+ return ImportCertificates(buf.get(), bytesObtained, aType, cxt);
+ case nsIX509Cert::EMAIL_CERT:
+ return ImportEmailCertificate(buf.get(), bytesObtained, cxt);
+ default:
+ MOZ_ASSERT(false, "Unsupported type should have been filtered out");
+ break;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsNSSCertificateDB::ImportPKCS12File(nsISupports* aToken, nsIFile* aFile)
+{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ NS_ENSURE_ARG(aFile);
+ nsPKCS12Blob blob;
+ nsCOMPtr<nsIPK11Token> token = do_QueryInterface(aToken);
+ if (token) {
+ blob.SetToken(token);
+ }
+ return blob.ImportFromFile(aFile);
+}
+
+NS_IMETHODIMP
+nsNSSCertificateDB::ExportPKCS12File(nsISupports* aToken,
+ nsIFile* aFile,
+ uint32_t count,
+ nsIX509Cert** certs)
+{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ NS_ENSURE_ARG(aFile);
+ nsPKCS12Blob blob;
+ if (count == 0) return NS_OK;
+ nsCOMPtr<nsIPK11Token> localRef;
+ if (!aToken) {
+ UniquePK11SlotInfo keySlot(PK11_GetInternalKeySlot());
+ if (!keySlot) {
+ return NS_ERROR_FAILURE;
+ }
+ localRef = new nsPK11Token(keySlot.get());
+ } else {
+ localRef = do_QueryInterface(aToken);
+ }
+ blob.SetToken(localRef);
+ return blob.ExportToFile(aFile, certs, count);
+}
+
+NS_IMETHODIMP
+nsNSSCertificateDB::FindEmailEncryptionCert(const nsAString& aNickname,
+ nsIX509Cert** _retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = nullptr;
+
+ if (aNickname.IsEmpty())
+ return NS_OK;
+
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCOMPtr<nsIInterfaceRequestor> ctx = new PipUIContext();
+ char *asciiname = nullptr;
+ NS_ConvertUTF16toUTF8 aUtf8Nickname(aNickname);
+ asciiname = const_cast<char*>(aUtf8Nickname.get());
+
+ /* Find a good cert in the user's database */
+ UniqueCERTCertificate cert(CERT_FindUserCertByUsage(CERT_GetDefaultCertDB(),
+ asciiname,
+ certUsageEmailRecipient,
+ true, ctx));
+ if (!cert) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIX509Cert> nssCert = nsNSSCertificate::Create(cert.get());
+ if (!nssCert) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ nssCert.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNSSCertificateDB::FindEmailSigningCert(const nsAString& aNickname,
+ nsIX509Cert** _retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = nullptr;
+
+ if (aNickname.IsEmpty())
+ return NS_OK;
+
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCOMPtr<nsIInterfaceRequestor> ctx = new PipUIContext();
+ char *asciiname = nullptr;
+ NS_ConvertUTF16toUTF8 aUtf8Nickname(aNickname);
+ asciiname = const_cast<char*>(aUtf8Nickname.get());
+
+ /* Find a good cert in the user's database */
+ UniqueCERTCertificate cert(CERT_FindUserCertByUsage(CERT_GetDefaultCertDB(),
+ asciiname,
+ certUsageEmailSigner,
+ true, ctx));
+ if (!cert) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIX509Cert> nssCert = nsNSSCertificate::Create(cert.get());
+ if (!nssCert) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ nssCert.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNSSCertificateDB::FindCertByEmailAddress(const char* aEmailAddress,
+ nsIX509Cert** _retval)
+{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ RefPtr<SharedCertVerifier> certVerifier(GetDefaultCertVerifier());
+ NS_ENSURE_TRUE(certVerifier, NS_ERROR_UNEXPECTED);
+
+ UniqueCERTCertList certlist(
+ PK11_FindCertsFromEmailAddress(aEmailAddress, nullptr));
+ if (!certlist)
+ return NS_ERROR_FAILURE;
+
+ // certlist now contains certificates with the right email address,
+ // but they might not have the correct usage or might even be invalid
+
+ if (CERT_LIST_END(CERT_LIST_HEAD(certlist), certlist))
+ return NS_ERROR_FAILURE; // no certs found
+
+ CERTCertListNode *node;
+ // search for a valid certificate
+ for (node = CERT_LIST_HEAD(certlist);
+ !CERT_LIST_END(node, certlist);
+ node = CERT_LIST_NEXT(node)) {
+
+ UniqueCERTCertList unusedCertChain;
+ mozilla::pkix::Result result =
+ certVerifier->VerifyCert(node->cert, certificateUsageEmailRecipient,
+ mozilla::pkix::Now(),
+ nullptr /*XXX pinarg*/,
+ nullptr /*hostname*/,
+ unusedCertChain);
+ if (result == mozilla::pkix::Success) {
+ break;
+ }
+ }
+
+ if (CERT_LIST_END(node, certlist)) {
+ // no valid cert found
+ return NS_ERROR_FAILURE;
+ }
+
+ // node now contains the first valid certificate with correct usage
+ RefPtr<nsNSSCertificate> nssCert = nsNSSCertificate::Create(node->cert);
+ if (!nssCert)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nssCert.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNSSCertificateDB::ConstructX509FromBase64(const nsACString& base64,
+ /*out*/ nsIX509Cert** _retval)
+{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ 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 ConstructX509(certDER.get(), certDER.Length(), _retval);
+}
+
+NS_IMETHODIMP
+nsNSSCertificateDB::ConstructX509(const char* certDER,
+ uint32_t lengthDER,
+ nsIX509Cert** _retval)
+{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ if (NS_WARN_IF(!_retval)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+
+ SECItem secitem_cert;
+ secitem_cert.type = siDERCertBuffer;
+ secitem_cert.data = (unsigned char*)certDER;
+ secitem_cert.len = lengthDER;
+
+ UniqueCERTCertificate cert(CERT_NewTempCertificate(CERT_GetDefaultCertDB(),
+ &secitem_cert, nullptr,
+ false, true));
+ if (!cert)
+ return (PORT_GetError() == SEC_ERROR_NO_MEMORY)
+ ? NS_ERROR_OUT_OF_MEMORY : NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIX509Cert> nssCert = nsNSSCertificate::Create(cert.get());
+ if (!nssCert) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ nssCert.forget(_retval);
+ return NS_OK;
+}
+
+void
+nsNSSCertificateDB::get_default_nickname(CERTCertificate *cert,
+ nsIInterfaceRequestor* ctx,
+ nsCString &nickname,
+ const nsNSSShutDownPreventionLock &/*proofOfLock*/)
+{
+ static NS_DEFINE_CID(kNSSComponentCID, NS_NSSCOMPONENT_CID);
+
+ nickname.Truncate();
+
+ nsresult rv;
+ CK_OBJECT_HANDLE keyHandle;
+
+ CERTCertDBHandle *defaultcertdb = CERT_GetDefaultCertDB();
+ nsCOMPtr<nsINSSComponent> nssComponent(do_GetService(kNSSComponentCID, &rv));
+ if (NS_FAILED(rv))
+ return;
+
+ 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;
+ nssComponent->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,
+ const nsACString& /*aName*/)
+{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsNSSCertTrust trust;
+ if (CERT_DecodeTrustString(trust.GetTrust(), PromiseFlatCString(aTrust).get())
+ != SECSuccess) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIX509Cert> newCert;
+ nsresult rv = ConstructX509FromBase64(aBase64, 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) {
+ return SetCertTrustFromString(newCert, aTrust);
+ }
+
+ UniquePORTString nickname(CERT_MakeCANickname(tmpCert.get()));
+
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("Created nick \"%s\"\n", nickname.get()));
+
+ rv = attemptToLogInWithDefaultPassword();
+ if (NS_WARN_IF(rv != NS_OK)) {
+ return rv;
+ }
+
+ SECStatus srv = CERT_AddTempCertToPerm(tmpCert.get(), nickname.get(),
+ trust.GetTrust());
+ return MapSECStatus(srv);
+}
+
+NS_IMETHODIMP
+nsNSSCertificateDB::AddCert(const nsACString& aCertDER, const nsACString& aTrust,
+ const nsACString& aName)
+{
+ nsCString base64;
+ nsresult rv = Base64Encode(aCertDER, base64);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return AddCertFromBase64(base64, aTrust, aName);
+}
+
+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());
+
+ nsresult rv = attemptToLogInWithDefaultPassword();
+ if (NS_WARN_IF(rv != NS_OK)) {
+ return rv;
+ }
+
+ srv = CERT_ChangeCertTrust(CERT_GetDefaultCertDB(), nssCert.get(), &trust);
+ return MapSECStatus(srv);
+}
+
+NS_IMETHODIMP
+nsNSSCertificateDB::GetCerts(nsIX509CertList **_retval)
+{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCOMPtr<nsIInterfaceRequestor> ctx = new PipUIContext();
+ nsCOMPtr<nsIX509CertList> nssCertList;
+ UniqueCERTCertList certList(PK11_ListCerts(PK11CertListUnique, ctx));
+
+ // nsNSSCertList 1) adopts certList, and 2) handles the nullptr case fine.
+ // (returns an empty list)
+ nssCertList = new nsNSSCertList(Move(certList), locker);
+
+ nssCertList.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNSSCertificateDB::GetEnterpriseRoots(nsIX509CertList** enterpriseRoots)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!NS_IsMainThread()) {
+ return NS_ERROR_NOT_SAME_THREAD;
+ }
+
+ NS_ENSURE_ARG_POINTER(enterpriseRoots);
+
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+#ifdef XP_WIN
+ nsCOMPtr<nsINSSComponent> psm(do_GetService(PSM_COMPONENT_CONTRACTID));
+ if (!psm) {
+ return NS_ERROR_FAILURE;
+ }
+ return psm->GetEnterpriseRoots(enterpriseRoots);
+#else
+ return NS_ERROR_NOT_IMPLEMENTED;
+#endif
+}
+
+nsresult
+VerifyCertAtTime(nsIX509Cert* aCert,
+ int64_t /*SECCertificateUsage*/ aUsage,
+ uint32_t aFlags,
+ const char* aHostname,
+ mozilla::pkix::Time aTime,
+ nsIX509CertList** aVerifiedChain,
+ bool* aHasEVPolicy,
+ int32_t* /*PRErrorCode*/ _retval,
+ const nsNSSShutDownPreventionLock& locker)
+{
+ NS_ENSURE_ARG_POINTER(aCert);
+ NS_ENSURE_ARG_POINTER(aHasEVPolicy);
+ NS_ENSURE_ARG_POINTER(aVerifiedChain);
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ *aVerifiedChain = nullptr;
+ *aHasEVPolicy = false;
+ *_retval = PR_UNKNOWN_ERROR;
+
+ UniqueCERTCertificate nssCert(aCert->GetCert());
+ if (!nssCert) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ RefPtr<SharedCertVerifier> certVerifier(GetDefaultCertVerifier());
+ NS_ENSURE_TRUE(certVerifier, NS_ERROR_FAILURE);
+
+ UniqueCERTCertList resultChain;
+ SECOidTag evOidPolicy;
+ mozilla::pkix::Result result;
+
+ if (aHostname && aUsage == certificateUsageSSLServer) {
+ result = certVerifier->VerifySSLServerCert(nssCert,
+ nullptr, // stapledOCSPResponse
+ nullptr, // sctsFromTLSExtension
+ aTime,
+ nullptr, // Assume no context
+ aHostname,
+ resultChain,
+ false, // don't save intermediates
+ aFlags,
+ NeckoOriginAttributes(),
+ &evOidPolicy);
+ } else {
+ result = certVerifier->VerifyCert(nssCert.get(), aUsage, aTime,
+ nullptr, // Assume no context
+ aHostname,
+ resultChain,
+ aFlags,
+ nullptr, // stapledOCSPResponse
+ nullptr, // sctsFromTLSExtension
+ NeckoOriginAttributes(),
+ &evOidPolicy);
+ }
+
+ nsCOMPtr<nsIX509CertList> nssCertList;
+ // This adopts the list
+ nssCertList = new nsNSSCertList(Move(resultChain), locker);
+ NS_ENSURE_TRUE(nssCertList, NS_ERROR_FAILURE);
+
+ *_retval = mozilla::pkix::MapResultToPRErrorCode(result);
+ if (result == mozilla::pkix::Success && evOidPolicy != SEC_OID_UNKNOWN) {
+ *aHasEVPolicy = true;
+ }
+ nssCertList.forget(aVerifiedChain);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNSSCertificateDB::VerifyCertNow(nsIX509Cert* aCert,
+ int64_t /*SECCertificateUsage*/ aUsage,
+ uint32_t aFlags,
+ const char* aHostname,
+ nsIX509CertList** aVerifiedChain,
+ bool* aHasEVPolicy,
+ int32_t* /*PRErrorCode*/ _retval)
+{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return ::VerifyCertAtTime(aCert, aUsage, aFlags, aHostname,
+ mozilla::pkix::Now(),
+ aVerifiedChain, aHasEVPolicy, _retval, locker);
+}
+
+NS_IMETHODIMP
+nsNSSCertificateDB::VerifyCertAtTime(nsIX509Cert* aCert,
+ int64_t /*SECCertificateUsage*/ aUsage,
+ uint32_t aFlags,
+ const char* aHostname,
+ uint64_t aTime,
+ nsIX509CertList** aVerifiedChain,
+ bool* aHasEVPolicy,
+ int32_t* /*PRErrorCode*/ _retval)
+{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return ::VerifyCertAtTime(aCert, aUsage, aFlags, aHostname,
+ mozilla::pkix::TimeFromEpochInSeconds(aTime),
+ aVerifiedChain, aHasEVPolicy, _retval, locker);
+}
+
+class VerifyCertAtTimeTask final : public CryptoTask
+{
+public:
+ VerifyCertAtTimeTask(nsIX509Cert* aCert, int64_t aUsage, uint32_t aFlags,
+ const char* aHostname, uint64_t aTime,
+ nsICertVerificationCallback* aCallback)
+ : mCert(aCert)
+ , mUsage(aUsage)
+ , mFlags(aFlags)
+ , mHostname(aHostname)
+ , mTime(aTime)
+ , mCallback(new nsMainThreadPtrHolder<nsICertVerificationCallback>(aCallback))
+ , mPRErrorCode(SEC_ERROR_LIBRARY_FAILURE)
+ , mVerifiedCertList(nullptr)
+ , mHasEVPolicy(false)
+ {
+ }
+
+private:
+ virtual nsresult CalculateResult() override
+ {
+ nsCOMPtr<nsIX509CertDB> certDB = do_GetService(NS_X509CERTDB_CONTRACTID);
+ if (!certDB) {
+ return NS_ERROR_FAILURE;
+ }
+ // Unfortunately mHostname will have made the empty string out of a null
+ // pointer passed in the constructor. If we pass the empty string on to
+ // VerifyCertAtTime with the usage certificateUsageSSLServer, it will call
+ // VerifySSLServerCert, which expects a non-empty hostname. To avoid this,
+ // check the length and use nullptr if appropriate.
+ const char* hostname = mHostname.Length() > 0 ? mHostname.get() : nullptr;
+ return certDB->VerifyCertAtTime(mCert, mUsage, mFlags, hostname, mTime,
+ getter_AddRefs(mVerifiedCertList),
+ &mHasEVPolicy, &mPRErrorCode);
+ }
+
+ // No NSS resources are directly held, so there is nothing to release.
+ virtual void ReleaseNSSResources() override { }
+
+ virtual void CallCallback(nsresult rv) override
+ {
+ if (NS_FAILED(rv)) {
+ Unused << mCallback->VerifyCertFinished(SEC_ERROR_LIBRARY_FAILURE,
+ nullptr, false);
+ } else {
+ Unused << mCallback->VerifyCertFinished(mPRErrorCode, mVerifiedCertList,
+ mHasEVPolicy);
+ }
+ }
+
+ nsCOMPtr<nsIX509Cert> mCert;
+ int64_t mUsage;
+ uint32_t mFlags;
+ nsCString mHostname;
+ uint64_t mTime;
+ nsMainThreadPtrHandle<nsICertVerificationCallback> mCallback;
+ int32_t mPRErrorCode;
+ nsCOMPtr<nsIX509CertList> mVerifiedCertList;
+ bool mHasEVPolicy;
+};
+
+NS_IMETHODIMP
+nsNSSCertificateDB::AsyncVerifyCertAtTime(nsIX509Cert* aCert,
+ int64_t /*SECCertificateUsage*/ aUsage,
+ uint32_t aFlags,
+ const char* aHostname,
+ uint64_t aTime,
+ nsICertVerificationCallback* aCallback)
+{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ RefPtr<VerifyCertAtTimeTask> task(new VerifyCertAtTimeTask(aCert, aUsage,
+ aFlags, aHostname,
+ aTime, aCallback));
+ return task->Dispatch("VerifyCert");
+}
+
+NS_IMETHODIMP
+nsNSSCertificateDB::ClearOCSPCache()
+{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ RefPtr<SharedCertVerifier> certVerifier(GetDefaultCertVerifier());
+ NS_ENSURE_TRUE(certVerifier, NS_ERROR_FAILURE);
+ certVerifier->ClearOCSPCache();
+ return NS_OK;
+}