/* 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/Assertions.h" #include "mozilla/Base64.h" #include "mozilla/Casting.h" #include "mozilla/Logging.h" #include "mozilla/Services.h" #include "mozilla/Unused.h" #include "mozpkix/Time.h" #include "mozpkix/pkixnss.h" #include "mozpkix/pkixtypes.h" #include "nsArray.h" #include "nsArrayUtils.h" #include "nsCOMPtr.h" #include "nsComponentManagerUtils.h" #include "nsICertificateDialogs.h" #include "nsIFile.h" #include "nsIMutableArray.h" #include "nsIObserverService.h" #include "nsIPrompt.h" #include "nsNSSCertHelper.h" #include "nsNSSCertTrust.h" #include "nsNSSCertificate.h" #include "nsNSSComponent.h" #include "nsNSSHelper.h" #include "nsPKCS12Blob.h" #include "nsPromiseFlatString.h" #include "nsProxyRelease.h" #include "nsReadableUtils.h" #include "nsThreadUtils.h" #include "nspr.h" #include "secasn1.h" #include "secder.h" #include "secerr.h" #include "ssl.h" #ifdef XP_WIN # include // for ntohl #endif using namespace mozilla; using namespace mozilla::psm; extern LazyLogModule gPIPNSSLog; NS_IMPL_ISUPPORTS(nsNSSCertificateDB, nsIX509CertDB) NS_IMETHODIMP nsNSSCertificateDB::FindCertByDBKey(const nsACString& aDBKey, /*out*/ nsIX509Cert** _cert) { NS_ENSURE_ARG_POINTER(_cert); *_cert = nullptr; if (aDBKey.IsEmpty()) { return NS_ERROR_INVALID_ARG; } nsresult rv = BlockUntilLoadableCertsLoaded(); if (NS_FAILED(rv)) { return rv; } UniqueCERTCertificate cert; rv = FindCertByDBKey(aDBKey, cert); if (NS_FAILED(rv)) { return rv; } // If we can't find the certificate, that's not an error. Just return null. if (!cert) { return NS_OK; } nsCOMPtr nssCert = nsNSSCertificate::Create(cert.get()); if (!nssCert) { return NS_ERROR_OUT_OF_MEMORY; } nssCert.forget(_cert); return NS_OK; } nsresult nsNSSCertificateDB::FindCertByDBKey(const nsACString& aDBKey, UniqueCERTCertificate& cert) { static_assert(sizeof(uint64_t) == 8, "type size sanity check"); static_assert(sizeof(uint32_t) == 4, "type size sanity check"); // (From nsNSSCertificate::GetDbKey) // The format of the key is the base64 encoding of the following: // 4 bytes: {0, 0, 0, 0} (this was intended to be the module ID, but it was // never implemented) // 4 bytes: {0, 0, 0, 0} (this was intended to be the slot ID, but it was // never implemented) // 4 bytes: // 4 bytes: // n bytes: // m bytes: 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(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(reader))); reader += sizeof(uint32_t); uint32_t issuerLen = ntohl((*BitwiseCast(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(reader); reader += serialNumberLen; issuerSN.derIssuer.len = issuerLen; issuerSN.derIssuer.data = BitwiseCast(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) { nsTArray>* certsArray = reinterpret_cast>*>(arg); while (numcerts--) { nsTArray certArray; SECItem* cert = *certs; certArray.AppendElements(cert->data, cert->len); certsArray->AppendElement(std::move(certArray)); certs++; } return (SECSuccess); } nsresult nsNSSCertificateDB::getCertsFromPackage( nsTArray>& collectArgs, uint8_t* data, uint32_t length) { if (CERT_DecodeCertPackage(BitwiseCast(data), length, collect_certs, &collectArgs) != SECSuccess) { return NS_ERROR_FAILURE; } return NS_OK; } // When using the sql-backed softoken, trust settings are authenticated using a // key in the secret database. Thus, if the user has a password, we need to // authenticate to the token in order to be able to change trust settings. SECStatus ChangeCertTrustWithPossibleAuthentication( const UniqueCERTCertificate& cert, CERTCertTrust& trust, void* ctx) { MOZ_ASSERT(cert, "cert must be non-null"); if (!cert) { PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0); return SECFailure; } // NSS ignores the first argument to CERT_ChangeCertTrust SECStatus srv = CERT_ChangeCertTrust(nullptr, cert.get(), &trust); if (srv == SECSuccess || PR_GetError() != SEC_ERROR_TOKEN_NOT_LOGGED_IN) { return srv; } if (cert->slot) { // If this certificate is on an external PKCS#11 token, we have to // authenticate to that token. srv = PK11_Authenticate(cert->slot, PR_TRUE, ctx); } else { // Otherwise, the certificate is on the internal module. UniquePK11SlotInfo internalSlot(PK11_GetInternalKeySlot()); srv = PK11_Authenticate(internalSlot.get(), PR_TRUE, ctx); } if (srv != SECSuccess) { return srv; } return CERT_ChangeCertTrust(nullptr, cert.get(), &trust); } static nsresult ImportCertsIntoPermanentStorage( const UniqueCERTCertList& certChain) { bool encounteredFailure = false; PRErrorCode savedErrorCode = 0; UniquePK11SlotInfo slot(PK11_GetInternalKeySlot()); for (CERTCertListNode* chainNode = CERT_LIST_HEAD(certChain); !CERT_LIST_END(chainNode, certChain); chainNode = CERT_LIST_NEXT(chainNode)) { UniquePORTString nickname(CERT_MakeCANickname(chainNode->cert)); SECStatus srv = PK11_ImportCert(slot.get(), chainNode->cert, CK_INVALID_HANDLE, nickname.get(), false); // this parameter is ignored by NSS if (srv != SECSuccess) { encounteredFailure = true; savedErrorCode = PR_GetError(); } } if (encounteredFailure) { return GetXPCOMFromNSSError(savedErrorCode); } return NS_OK; } nsresult nsNSSCertificateDB::handleCACertDownload(NotNull x509Certs, nsIInterfaceRequestor* ctx) { // First thing we have to do is figure out which certificate we're // gonna present to the user. The CA may have sent down a list of // certs which may or may not be a chained list of certs. Until // the day we can design some solid UI for the general case, we'll // code to the > 90% case. That case is where a CA sends down a // list that is a hierarchy whose root is either the first or // the last cert. What we're gonna do is compare the first // 2 entries, if the second was signed by the first, we assume // the root cert is the first cert and display it. Otherwise, // we compare the last 2 entries, if the second to last cert was // signed by the last cert, then we assume the last cert is the // root and display it. uint32_t numCerts; x509Certs->GetLength(&numCerts); if (numCerts == 0) return NS_OK; // Nothing to import, so nothing to do. nsCOMPtr 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 cert0; // first cert nsCOMPtr cert1; // second cert nsCOMPtr certn_2; // second to last cert nsCOMPtr certn_1; // last cert cert0 = do_QueryElementAt(x509Certs, 0); cert1 = do_QueryElementAt(x509Certs, 1); certn_2 = do_QueryElementAt(x509Certs, numCerts - 2); certn_1 = do_QueryElementAt(x509Certs, numCerts - 1); nsAutoString cert0SubjectName; nsAutoString cert1IssuerName; nsAutoString certn_2IssuerName; nsAutoString certn_1SubjectName; cert0->GetSubjectName(cert0SubjectName); cert1->GetIssuerName(cert1IssuerName); certn_2->GetIssuerName(certn_2IssuerName); certn_1->GetSubjectName(certn_1SubjectName); if (cert1IssuerName.Equals(cert0SubjectName)) { // In this case, the first cert in the list signed the second, // so the first cert is the root. Let's display it. selCertIndex = 0; certToShow = cert0; } else if (certn_2IssuerName.Equals(certn_1SubjectName)) { // In this case the last cert has signed the second to last cert. // The last cert is the root, so let's display it. selCertIndex = numCerts - 1; certToShow = certn_1; } else { // It's not a chain, so let's just show the first one in the // downloaded list. selCertIndex = 0; certToShow = cert0; } } if (!certToShow) return NS_ERROR_FAILURE; nsCOMPtr 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); return NS_ERROR_FAILURE; } if (tmpCert->isperm) { DisplayCertificateAlert(ctx, "CaCertExists", certToShow); 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)); UniquePK11SlotInfo slot(PK11_GetInternalKeySlot()); SECStatus srv = PK11_ImportCert(slot.get(), tmpCert.get(), CK_INVALID_HANDLE, nickname.get(), false); // this parameter is ignored by NSS if (srv != SECSuccess) { return MapSECStatus(srv); } srv = ChangeCertTrustWithPossibleAuthentication(tmpCert, trust.GetTrust(), ctx); if (srv != SECSuccess) { return MapSECStatus(srv); } // Import additional delivered certificates that can be verified. // build a CertList for filtering UniqueCERTCertList certList(CERT_NewCertList()); if (!certList) { return NS_ERROR_FAILURE; } // get all remaining certs into temp store for (uint32_t i = 0; i < numCerts; i++) { if (i == selCertIndex) { // we already processed that one continue; } nsCOMPtr 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 ImportCertsIntoPermanentStorage(certList); } nsresult nsNSSCertificateDB::ConstructCertArrayFromUniqueCertList( const UniqueCERTCertList& aCertListIn, nsTArray>& aCertListOut) { if (!aCertListIn.get()) { return NS_ERROR_INVALID_ARG; } for (CERTCertListNode* node = CERT_LIST_HEAD(aCertListIn.get()); !CERT_LIST_END(node, aCertListIn.get()); node = CERT_LIST_NEXT(node)) { RefPtr cert = nsNSSCertificate::Create(node->cert); if (!cert) { return NS_ERROR_OUT_OF_MEMORY; } aCertListOut.AppendElement(cert); } return NS_OK; } NS_IMETHODIMP nsNSSCertificateDB::ImportCertificates(uint8_t* data, uint32_t length, uint32_t type, nsIInterfaceRequestor* ctx) { // We currently only handle CA certificates. if (type != nsIX509Cert::CA_CERT) { return NS_ERROR_FAILURE; } nsTArray> certsArray; nsresult rv = getCertsFromPackage(certsArray, data, length); if (NS_FAILED(rv)) { return rv; } nsCOMPtr array = nsArrayBase::Create(); if (!array) { return NS_ERROR_FAILURE; } // Now let's create some certs to work with for (nsTArray& certDER : certsArray) { nsCOMPtr cert = nsNSSCertificate::ConstructFromDER( BitwiseCast(certDER.Elements()), certDER.Length()); if (!cert) { return NS_ERROR_FAILURE; } nsresult rv = array->AppendElement(cert); if (NS_FAILED(rv)) { return rv; } } return handleCACertDownload(WrapNotNull(array), ctx); } /** * Decodes a given array of DER-encoded certificates into temporary storage. * * @param certs * Array in which the decoded certificates are stored as arrays of * unsigned chars. * @param temporaryCerts * List of decoded certificates. */ static nsresult ImportCertsIntoTempStorage( nsTArray>& certs, /*out*/ const UniqueCERTCertList& temporaryCerts) { NS_ENSURE_ARG_POINTER(temporaryCerts); for (nsTArray& certDER : certs) { CERTCertificate* certificate; SECItem certItem; certItem.len = certDER.Length(); certItem.data = certDER.Elements(); certificate = CERT_NewTempCertificate(CERT_GetDefaultCertDB(), &certItem, nullptr, false, true); UniqueCERTCertificate cert(certificate); if (!cert) { continue; } if (CERT_AddCertToListTail(temporaryCerts.get(), cert.get()) == SECSuccess) { Unused << cert.release(); } } return NS_OK; } NS_IMETHODIMP nsNSSCertificateDB::ImportEmailCertificate(uint8_t* data, uint32_t length, nsIInterfaceRequestor* ctx) { nsTArray> certsArray; nsresult rv = getCertsFromPackage(certsArray, data, length); if (NS_FAILED(rv)) { return rv; } UniqueCERTCertList temporaryCerts(CERT_NewCertList()); if (!temporaryCerts) { return NS_ERROR_FAILURE; } rv = ImportCertsIntoTempStorage(certsArray, temporaryCerts); if (NS_FAILED(rv)) { return rv; } return ImportCertsIntoPermanentStorage(temporaryCerts); } nsresult nsNSSCertificateDB::ImportCACerts(nsTArray>& caCerts, nsIInterfaceRequestor* ctx) { UniqueCERTCertList temporaryCerts(CERT_NewCertList()); if (!temporaryCerts) { return NS_ERROR_FAILURE; } nsresult rv = ImportCertsIntoTempStorage(caCerts, temporaryCerts); if (NS_FAILED(rv)) { return rv; } return ImportCertsIntoPermanentStorage(temporaryCerts); } void nsNSSCertificateDB::DisplayCertificateAlert(nsIInterfaceRequestor* ctx, const char* stringID, nsIX509Cert* certToShow) { if (!NS_IsMainThread()) { NS_ERROR( "nsNSSCertificateDB::DisplayCertificateAlert called off the main " "thread"); return; } nsCOMPtr 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. nsAutoString tmpMessage; GetPIPNSSBundleString(stringID, tmpMessage); nsCOMPtr 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; } nsTArray> certsArray; nsresult rv = getCertsFromPackage(certsArray, data, length); if (NS_FAILED(rv)) { return rv; } SECItem certItem; if (certsArray.IsEmpty()) { return NS_OK; } certItem.len = certsArray.ElementAt(0).Length(); certItem.data = certsArray.ElementAt(0).Elements(); UniqueCERTCertificate cert(CERT_NewTempCertificate( CERT_GetDefaultCertDB(), &certItem, nullptr, false, true)); if (!cert) { return NS_ERROR_FAILURE; } UniquePK11SlotInfo slot(PK11_KeyForCertExists(cert.get(), nullptr, ctx)); if (!slot) { nsCOMPtr certToShow = nsNSSCertificate::Create(cert.get()); DisplayCertificateAlert(ctx, "UserCertIgnoredNoPrivateKey", certToShow); return NS_ERROR_FAILURE; } slot = nullptr; /* pick a nickname for the cert */ nsAutoCString nickname; if (cert->nickname) { nickname = cert->nickname; } else { get_default_nickname(cert.get(), ctx, nickname); } /* user wants to import the cert */ slot.reset(PK11_ImportCertForKey(cert.get(), nickname.get(), ctx)); if (!slot) { return NS_ERROR_FAILURE; } slot = nullptr; { nsCOMPtr certToShow = nsNSSCertificate::Create(cert.get()); DisplayCertificateAlert(ctx, "UserCertImported", certToShow); } rv = NS_OK; if (!certsArray.IsEmpty()) { certsArray.RemoveElementAt(0); rv = ImportCACerts(certsArray, ctx); } nsCOMPtr observerService = mozilla::services::GetObserverService(); if (observerService) { observerService->NotifyObservers(nullptr, "psm:user-certificate-added", nullptr); } return rv; } NS_IMETHODIMP nsNSSCertificateDB::DeleteCertificate(nsIX509Cert* aCert) { NS_ENSURE_ARG_POINTER(aCert); UniqueCERTCertificate cert(aCert->GetCert()); if (!cert) { return NS_ERROR_FAILURE; } // Temporary certificates aren't on a slot and will go away when the // nsIX509Cert is destructed. if (cert->slot) { uint32_t certType; nsresult rv = aCert->GetCertType(&certType); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (certType == nsIX509Cert::USER_CERT) { SECStatus srv = PK11_Authenticate(cert->slot, true, nullptr); if (srv != SECSuccess) { return NS_ERROR_FAILURE; } srv = PK11_DeleteTokenCertAndKey(cert.get(), nullptr); if (srv != SECSuccess) { return NS_ERROR_FAILURE; } } else { // For certificates that can't be deleted (e.g. built-in roots), un-set // all trust bits. nsNSSCertTrust trust(0, 0); SECStatus srv = ChangeCertTrustWithPossibleAuthentication( cert, trust.GetTrust(), nullptr); if (srv != SECSuccess) { return NS_ERROR_FAILURE; } if (!PK11_IsReadOnly(cert->slot)) { srv = SEC_DeletePermCertificate(cert.get()); if (srv != SECSuccess) { return NS_ERROR_FAILURE; } } } } nsCOMPtr observerService = mozilla::services::GetObserverService(); if (observerService) { observerService->NotifyObservers(nullptr, "psm:user-certificate-deleted", nullptr); } return NS_OK; } NS_IMETHODIMP nsNSSCertificateDB::SetCertTrust(nsIX509Cert* cert, uint32_t type, uint32_t trusted) { NS_ENSURE_ARG_POINTER(cert); nsNSSCertTrust trust; switch (type) { case nsIX509Cert::CA_CERT: trust.SetValidCA(); trust.AddCATrust(!!(trusted & nsIX509CertDB::TRUSTED_SSL), !!(trusted & nsIX509CertDB::TRUSTED_EMAIL)); break; case nsIX509Cert::SERVER_CERT: trust.SetValidPeer(); trust.AddPeerTrust(trusted & nsIX509CertDB::TRUSTED_SSL, false); break; case nsIX509Cert::EMAIL_CERT: trust.SetValidPeer(); trust.AddPeerTrust(false, !!(trusted & nsIX509CertDB::TRUSTED_EMAIL)); break; default: // Ignore any other type of certificate (including invalid types). return NS_OK; } UniqueCERTCertificate nsscert(cert->GetCert()); SECStatus srv = ChangeCertTrustWithPossibleAuthentication( nsscert, trust.GetTrust(), nullptr); return MapSECStatus(srv); } NS_IMETHODIMP nsNSSCertificateDB::IsCertTrusted(nsIX509Cert* cert, uint32_t certType, uint32_t trustType, bool* _isTrusted) { NS_ENSURE_ARG_POINTER(_isTrusted); *_isTrusted = false; nsresult rv = BlockUntilLoadableCertsLoaded(); if (NS_FAILED(rv)) { return rv; } SECStatus srv; UniqueCERTCertificate nsscert(cert->GetCert()); CERTCertTrust nsstrust; srv = CERT_GetCertTrust(nsscert.get(), &nsstrust); if (srv != SECSuccess) { // CERT_GetCertTrust returns SECFailure if given a temporary cert that // doesn't have any trust information yet. This isn't an error. return NS_OK; } nsNSSCertTrust trust(&nsstrust); if (certType == nsIX509Cert::CA_CERT) { if (trustType & nsIX509CertDB::TRUSTED_SSL) { *_isTrusted = trust.HasTrustedCA(true, false); } else if (trustType & nsIX509CertDB::TRUSTED_EMAIL) { *_isTrusted = trust.HasTrustedCA(false, true); } else { return NS_ERROR_FAILURE; } } else if (certType == nsIX509Cert::SERVER_CERT) { if (trustType & nsIX509CertDB::TRUSTED_SSL) { *_isTrusted = trust.HasTrustedPeer(true, false); } else if (trustType & nsIX509CertDB::TRUSTED_EMAIL) { *_isTrusted = trust.HasTrustedPeer(false, true); } else { return NS_ERROR_FAILURE; } } else if (certType == nsIX509Cert::EMAIL_CERT) { if (trustType & nsIX509CertDB::TRUSTED_SSL) { *_isTrusted = trust.HasTrustedPeer(true, false); } else if (trustType & nsIX509CertDB::TRUSTED_EMAIL) { *_isTrusted = trust.HasTrustedPeer(false, true); } else { return NS_ERROR_FAILURE; } } /* user: ignore */ return NS_OK; } NS_IMETHODIMP nsNSSCertificateDB::ImportCertsFromFile(nsIFile* aFile, uint32_t aType) { NS_ENSURE_ARG(aFile); switch (aType) { case nsIX509Cert::CA_CERT: case nsIX509Cert::EMAIL_CERT: // good break; default: // not supported (yet) return NS_ERROR_FAILURE; } PRFileDesc* fd = nullptr; nsresult rv = aFile->OpenNSPRFileDesc(PR_RDONLY, 0, &fd); if (NS_FAILED(rv)) { return rv; } if (!fd) { return NS_ERROR_FAILURE; } PRFileInfo fileInfo; if (PR_GetOpenFileInfo(fd, &fileInfo) != PR_SUCCESS) { return NS_ERROR_FAILURE; } auto buf = MakeUnique(fileInfo.size); int32_t bytesObtained = PR_Read(fd, buf.get(), fileInfo.size); PR_Close(fd); if (bytesObtained != fileInfo.size) { return NS_ERROR_FAILURE; } nsCOMPtr 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(nsIFile* aFile, const nsAString& aPassword, uint32_t* aError) { if (!NS_IsMainThread()) { return NS_ERROR_NOT_SAME_THREAD; } nsresult rv = BlockUntilLoadableCertsLoaded(); if (NS_FAILED(rv)) { return rv; } NS_ENSURE_ARG(aFile); nsPKCS12Blob blob; rv = blob.ImportFromFile(aFile, aPassword, *aError); nsCOMPtr observerService = mozilla::services::GetObserverService(); if (NS_SUCCEEDED(rv) && observerService) { observerService->NotifyObservers(nullptr, "psm:user-certificate-added", nullptr); } return rv; } NS_IMETHODIMP nsNSSCertificateDB::ExportPKCS12File( nsIFile* aFile, const nsTArray>& aCerts, const nsAString& aPassword, uint32_t* aError) { if (!NS_IsMainThread()) { return NS_ERROR_NOT_SAME_THREAD; } nsresult rv = BlockUntilLoadableCertsLoaded(); if (NS_FAILED(rv)) { return rv; } NS_ENSURE_ARG(aFile); if (aCerts.IsEmpty()) { return NS_OK; } nsPKCS12Blob blob; return blob.ExportToFile(aFile, aCerts, aPassword, *aError); } NS_IMETHODIMP nsNSSCertificateDB::ConstructX509FromBase64(const nsACString& base64, /*out*/ nsIX509Cert** _retval) { if (!_retval) { return NS_ERROR_INVALID_POINTER; } // Base64Decode() doesn't consider a zero length input as an error, and just // returns the empty string. We don't want this behavior, so the below check // catches this case. if (base64.Length() < 1) { return NS_ERROR_ILLEGAL_VALUE; } nsAutoCString certDER; nsresult rv = Base64Decode(base64, certDER); if (NS_FAILED(rv)) { return rv; } return ConstructX509FromSpan(AsBytes(Span(certDER)), _retval); } NS_IMETHODIMP nsNSSCertificateDB::ConstructX509(const nsTArray& certDER, nsIX509Cert** _retval) { return ConstructX509FromSpan(Span(certDER.Elements(), certDER.Length()), _retval); } nsresult nsNSSCertificateDB::ConstructX509FromSpan( Span aInputSpan, nsIX509Cert** _retval) { if (NS_WARN_IF(!_retval)) { return NS_ERROR_INVALID_POINTER; } if (aInputSpan.Length() > std::numeric_limits::max()) { return NS_ERROR_ILLEGAL_VALUE; } SECItem certData; certData.type = siDERCertBuffer; certData.data = const_cast( reinterpret_cast(aInputSpan.Elements())); certData.len = aInputSpan.Length(); UniqueCERTCertificate cert(CERT_NewTempCertificate( CERT_GetDefaultCertDB(), &certData, nullptr, false, true)); if (!cert) return (PORT_GetError() == SEC_ERROR_NO_MEMORY) ? NS_ERROR_OUT_OF_MEMORY : NS_ERROR_FAILURE; nsCOMPtr 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) { nickname.Truncate(); CK_OBJECT_HANDLE keyHandle; if (NS_FAILED(BlockUntilLoadableCertsLoaded())) { return; } CERTCertDBHandle* defaultcertdb = CERT_GetDefaultCertDB(); nsAutoCString username; UniquePORTString tempCN(CERT_GetCommonName(&cert->subject)); if (tempCN) { username = tempCN.get(); } nsAutoCString caname; UniquePORTString tempIssuerOrg(CERT_GetOrgName(&cert->issuer)); if (tempIssuerOrg) { caname = tempIssuerOrg.get(); } nsAutoString tmpNickFmt; GetPIPNSSBundleString("nick_template", tmpNickFmt); NS_ConvertUTF16toUTF8 nickFmt(tmpNickFmt); nsAutoCString baseName; baseName.AppendPrintf(nickFmt.get(), username.get(), caname.get()); if (baseName.IsEmpty()) { return; } nickname = baseName; /* * We need to see if the private key exists on a token, if it does * then we need to check for nicknames that already exist on the smart * card. */ UniquePK11SlotInfo slot(PK11_KeyForCertExists(cert, &keyHandle, ctx)); if (!slot) return; if (!PK11_IsInternal(slot.get())) { nsAutoCString tmp; tmp.AppendPrintf("%s:%s", PK11_GetTokenName(slot.get()), baseName.get()); if (tmp.IsEmpty()) { nickname.Truncate(); return; } baseName = tmp; nickname = baseName; } int count = 1; while (true) { if (count > 1) { nsAutoCString tmp; tmp.AppendPrintf("%s #%d", baseName.get(), count); if (tmp.IsEmpty()) { nickname.Truncate(); return; } nickname = tmp; } UniqueCERTCertificate dummycert; if (PK11_IsInternal(slot.get())) { /* look up the nickname to make sure it isn't in use already */ dummycert.reset(CERT_FindCertByNickname(defaultcertdb, nickname.get())); } else { // Check the cert against others that already live on the smart card. dummycert.reset(PK11_FindCertFromNickname(nickname.get(), ctx)); if (dummycert) { // Make sure the subject names are different. if (CERT_CompareName(&cert->subject, &dummycert->subject) == SECEqual) { /* * There is another certificate with the same nickname and * the same subject name on the smart card, so let's use this * nickname. */ dummycert = nullptr; } } } if (!dummycert) { break; } count++; } } NS_IMETHODIMP nsNSSCertificateDB::AddCertFromBase64(const nsACString& aBase64, const nsACString& aTrust, nsIX509Cert** addedCertificate) { // Base64Decode() doesn't consider a zero length input as an error, and just // returns the empty string. We don't want this behavior, so the below check // catches this case. if (aBase64.Length() < 1) { return NS_ERROR_ILLEGAL_VALUE; } nsAutoCString aCertDER; nsresult rv = Base64Decode(aBase64, aCertDER); if (NS_FAILED(rv)) { return rv; } return AddCert(aCertDER, aTrust, addedCertificate); } NS_IMETHODIMP nsNSSCertificateDB::AddCert(const nsACString& aCertDER, const nsACString& aTrust, nsIX509Cert** addedCertificate) { MOZ_ASSERT(addedCertificate); if (!addedCertificate) { return NS_ERROR_INVALID_ARG; } *addedCertificate = nullptr; nsNSSCertTrust trust; if (CERT_DecodeTrustString(&trust.GetTrust(), PromiseFlatCString(aTrust).get()) != SECSuccess) { return NS_ERROR_FAILURE; } nsCOMPtr newCert; nsresult rv = ConstructX509FromSpan(AsBytes(Span(aCertDER)), getter_AddRefs(newCert)); if (NS_FAILED(rv)) { return rv; } UniqueCERTCertificate tmpCert(newCert->GetCert()); if (!tmpCert) { return NS_ERROR_FAILURE; } // If there's already a certificate that matches this one in the database, we // still want to set its trust to the given value. if (tmpCert->isperm) { rv = SetCertTrustFromString(newCert, aTrust); if (NS_FAILED(rv)) { return rv; } newCert.forget(addedCertificate); return NS_OK; } UniquePORTString nickname(CERT_MakeCANickname(tmpCert.get())); MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("Created nick \"%s\"\n", nickname.get())); UniquePK11SlotInfo slot(PK11_GetInternalKeySlot()); SECStatus srv = PK11_ImportCert(slot.get(), tmpCert.get(), CK_INVALID_HANDLE, nickname.get(), false); // this parameter is ignored by NSS if (srv != SECSuccess) { return MapSECStatus(srv); } srv = ChangeCertTrustWithPossibleAuthentication(tmpCert, trust.GetTrust(), nullptr); if (srv != SECSuccess) { return MapSECStatus(srv); } newCert.forget(addedCertificate); return NS_OK; } NS_IMETHODIMP nsNSSCertificateDB::SetCertTrustFromString(nsIX509Cert* cert, const nsACString& trustString) { NS_ENSURE_ARG(cert); CERTCertTrust trust; SECStatus srv = CERT_DecodeTrustString(&trust, PromiseFlatCString(trustString).get()); if (srv != SECSuccess) { return MapSECStatus(srv); } UniqueCERTCertificate nssCert(cert->GetCert()); srv = ChangeCertTrustWithPossibleAuthentication(nssCert, trust, nullptr); return MapSECStatus(srv); } NS_IMETHODIMP nsNSSCertificateDB::AsPKCS7Blob( const nsTArray>& certList, nsACString& _retval) { if (certList.IsEmpty()) { return NS_ERROR_INVALID_ARG; } UniqueNSSCMSMessage cmsg(NSS_CMSMessage_Create(nullptr)); if (!cmsg) { MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsNSSCertificateDB::AsPKCS7Blob - can't create CMS message")); return NS_ERROR_OUT_OF_MEMORY; } UniqueNSSCMSSignedData sigd(nullptr); for (const auto& cert : certList) { // We need an owning handle when calling nsIX509Cert::GetCert(). UniqueCERTCertificate nssCert(cert->GetCert()); if (!sigd) { sigd.reset( NSS_CMSSignedData_CreateCertsOnly(cmsg.get(), nssCert.get(), false)); if (!sigd) { MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsNSSCertificateDB::AsPKCS7Blob - can't create SignedData")); return NS_ERROR_FAILURE; } } else if (NSS_CMSSignedData_AddCertificate(sigd.get(), nssCert.get()) != SECSuccess) { MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsNSSCertificateDB::AsPKCS7Blob - can't add cert")); return NS_ERROR_FAILURE; } } NSSCMSContentInfo* cinfo = NSS_CMSMessage_GetContentInfo(cmsg.get()); if (NSS_CMSContentInfo_SetContent_SignedData(cmsg.get(), cinfo, sigd.get()) != SECSuccess) { MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsNSSCertificateDB::AsPKCS7Blob - can't attach SignedData")); return NS_ERROR_FAILURE; } // cmsg owns sigd now. Unused << sigd.release(); UniquePLArenaPool arena(PORT_NewArena(1024)); if (!arena) { MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsNSSCertificateDB::AsPKCS7Blob - out of memory")); return NS_ERROR_OUT_OF_MEMORY; } SECItem certP7 = {siBuffer, nullptr, 0}; NSSCMSEncoderContext* ecx = NSS_CMSEncoder_Start( cmsg.get(), nullptr, nullptr, &certP7, arena.get(), nullptr, nullptr, nullptr, nullptr, nullptr, nullptr); if (!ecx) { MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsNSSCertificateDB::AsPKCS7Blob - can't create encoder")); return NS_ERROR_FAILURE; } if (NSS_CMSEncoder_Finish(ecx) != SECSuccess) { MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsNSSCertificateDB::AsPKCS7Blob - failed to add encoded data")); return NS_ERROR_FAILURE; } _retval.Assign(nsDependentCSubstring( reinterpret_cast(certP7.data), certP7.len)); return NS_OK; } NS_IMETHODIMP nsNSSCertificateDB::GetCerts(nsTArray>& _retval) { nsresult rv = BlockUntilLoadableCertsLoaded(); if (NS_FAILED(rv)) { return rv; } rv = CheckForSmartCardChanges(); if (NS_FAILED(rv)) { return rv; } nsCOMPtr ctx = new PipUIContext(); UniqueCERTCertList certList(PK11_ListCerts(PK11CertListUnique, ctx)); if (!certList) { return NS_ERROR_FAILURE; } return nsNSSCertificateDB::ConstructCertArrayFromUniqueCertList(certList, _retval); } NS_IMETHODIMP nsNSSCertificateDB::AsyncHasThirdPartyRoots(nsIAsyncBoolCallback* aCallback) { NS_ENSURE_ARG_POINTER(aCallback); nsMainThreadPtrHandle callback( new nsMainThreadPtrHolder("AsyncHasThirdPartyRoots", aCallback)); return NS_DispatchBackgroundTask( NS_NewRunnableFunction( "nsNSSCertificateDB::AsyncHasThirdPartyRoots", [cb = std::move(callback), self = RefPtr{this}] { bool hasThirdPartyRoots = [self]() -> bool { nsTArray> certs; nsresult rv = self->GetCerts(certs); if (NS_FAILED(rv)) { return false; } for (const auto& cert : certs) { bool isTrusted = false; nsresult rv = self->IsCertTrusted(cert, nsIX509Cert::CA_CERT, nsIX509CertDB::TRUSTED_SSL, &isTrusted); if (NS_FAILED(rv)) { return false; } if (!isTrusted) { continue; } bool isBuiltInRoot = false; rv = cert->GetIsBuiltInRoot(&isBuiltInRoot); if (NS_FAILED(rv)) { return false; } if (!isBuiltInRoot) { return true; } } return false; }(); NS_DispatchToMainThread(NS_NewRunnableFunction( "nsNSSCertificateDB::AsyncHasThirdPartyRoots callback", [cb, hasThirdPartyRoots]() { cb->OnResult(hasThirdPartyRoots); })); }), NS_DISPATCH_EVENT_MAY_BLOCK); return NS_OK; } nsresult VerifyCertAtTime(nsIX509Cert* aCert, int64_t /*SECCertificateUsage*/ aUsage, uint32_t aFlags, const nsACString& aHostname, mozilla::pkix::Time aTime, nsTArray>& aVerifiedChain, bool* aHasEVPolicy, int32_t* /*PRErrorCode*/ _retval) { NS_ENSURE_ARG_POINTER(aCert); NS_ENSURE_ARG_POINTER(aHasEVPolicy); NS_ENSURE_ARG_POINTER(_retval); if (!aVerifiedChain.IsEmpty()) { return NS_ERROR_INVALID_ARG; } *aHasEVPolicy = false; *_retval = PR_UNKNOWN_ERROR; UniqueCERTCertificate nssCert(aCert->GetCert()); if (!nssCert) { return NS_ERROR_INVALID_ARG; } RefPtr certVerifier(GetDefaultCertVerifier()); NS_ENSURE_TRUE(certVerifier, NS_ERROR_FAILURE); UniqueCERTCertList resultChain; SECOidTag evOidPolicy; mozilla::pkix::Result result; if (!aHostname.IsVoid() && aUsage == certificateUsageSSLServer) { result = certVerifier->VerifySSLServerCert(nssCert, aTime, nullptr, // Assume no context aHostname, resultChain, aFlags, Nothing(), // extraCertificates Nothing(), // stapledOCSPResponse Nothing(), // sctsFromTLSExtension Nothing(), // dcInfo OriginAttributes(), false, // don't save intermediates &evOidPolicy); } else { const nsCString& flatHostname = PromiseFlatCString(aHostname); result = certVerifier->VerifyCert( nssCert.get(), aUsage, aTime, nullptr, // Assume no context aHostname.IsVoid() ? nullptr : flatHostname.get(), resultChain, aFlags, Nothing(), // extraCertificates Nothing(), // stapledOCSPResponse Nothing(), // sctsFromTLSExtension OriginAttributes(), &evOidPolicy); } if (result == mozilla::pkix::Success) { nsresult rv = nsNSSCertificateDB::ConstructCertArrayFromUniqueCertList( resultChain, aVerifiedChain); if (NS_FAILED(rv)) { return rv; } if (evOidPolicy != SEC_OID_UNKNOWN) { *aHasEVPolicy = true; } } *_retval = mozilla::pkix::MapResultToPRErrorCode(result); return NS_OK; } class VerifyCertAtTimeTask final : public CryptoTask { public: VerifyCertAtTimeTask(nsIX509Cert* aCert, int64_t aUsage, uint32_t aFlags, const nsACString& 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), mHasEVPolicy(false) {} private: virtual nsresult CalculateResult() override { nsCOMPtr certDB = do_GetService(NS_X509CERTDB_CONTRACTID); if (!certDB) { return NS_ERROR_FAILURE; } return VerifyCertAtTime(mCert, mUsage, mFlags, mHostname, mozilla::pkix::TimeFromEpochInSeconds(mTime), mVerifiedCertList, &mHasEVPolicy, &mPRErrorCode); } virtual void CallCallback(nsresult rv) override { if (NS_FAILED(rv)) { nsTArray> tmp; Unused << mCallback->VerifyCertFinished(SEC_ERROR_LIBRARY_FAILURE, tmp, false); } else { Unused << mCallback->VerifyCertFinished(mPRErrorCode, mVerifiedCertList, mHasEVPolicy); } } nsCOMPtr mCert; int64_t mUsage; uint32_t mFlags; nsCString mHostname; uint64_t mTime; nsMainThreadPtrHandle mCallback; int32_t mPRErrorCode; nsTArray> mVerifiedCertList; bool mHasEVPolicy; }; NS_IMETHODIMP nsNSSCertificateDB::AsyncVerifyCertAtTime( nsIX509Cert* aCert, int64_t /*SECCertificateUsage*/ aUsage, uint32_t aFlags, const nsACString& aHostname, uint64_t aTime, nsICertVerificationCallback* aCallback) { RefPtr task(new VerifyCertAtTimeTask( aCert, aUsage, aFlags, aHostname, aTime, aCallback)); return task->Dispatch(); } NS_IMETHODIMP nsNSSCertificateDB::ClearOCSPCache() { RefPtr certVerifier(GetDefaultCertVerifier()); NS_ENSURE_TRUE(certVerifier, NS_ERROR_FAILURE); certVerifier->ClearOCSPCache(); return NS_OK; }