diff options
Diffstat (limited to 'security/manager/ssl/nsNSSCertificateDB.cpp')
-rw-r--r-- | security/manager/ssl/nsNSSCertificateDB.cpp | 1658 |
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; +} |