michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: // See michael@0: // https://wiki.mozilla.org/Security/Features/Application_Reputation_Design_Doc michael@0: // for a description of Chrome's implementation of this feature. michael@0: #include "ApplicationReputation.h" michael@0: #include "csd.pb.h" michael@0: michael@0: #include "nsIArray.h" michael@0: #include "nsIApplicationReputation.h" michael@0: #include "nsIChannel.h" michael@0: #include "nsIHttpChannel.h" michael@0: #include "nsIIOService.h" michael@0: #include "nsIPrefService.h" michael@0: #include "nsIScriptSecurityManager.h" michael@0: #include "nsIStreamListener.h" michael@0: #include "nsIStringStream.h" michael@0: #include "nsIUploadChannel2.h" michael@0: #include "nsIURI.h" michael@0: #include "nsIUrlClassifierDBService.h" michael@0: #include "nsIX509Cert.h" michael@0: #include "nsIX509CertDB.h" michael@0: #include "nsIX509CertList.h" michael@0: michael@0: #include "mozilla/Preferences.h" michael@0: #include "mozilla/Services.h" michael@0: #include "mozilla/Telemetry.h" michael@0: #include "mozilla/TimeStamp.h" michael@0: #include "mozilla/LoadContext.h" michael@0: michael@0: #include "nsAutoPtr.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsDebug.h" michael@0: #include "nsError.h" michael@0: #include "nsNetCID.h" michael@0: #include "nsReadableUtils.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "nsString.h" michael@0: #include "nsTArray.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsXPCOMStrings.h" michael@0: michael@0: using mozilla::Preferences; michael@0: using mozilla::TimeStamp; michael@0: using mozilla::Telemetry::Accumulate; michael@0: using safe_browsing::ClientDownloadRequest; michael@0: using safe_browsing::ClientDownloadRequest_SignatureInfo; michael@0: using safe_browsing::ClientDownloadRequest_CertificateChain; michael@0: michael@0: // Preferences that we need to initialize the query. michael@0: #define PREF_SB_APP_REP_URL "browser.safebrowsing.appRepURL" michael@0: #define PREF_SB_MALWARE_ENABLED "browser.safebrowsing.malware.enabled" michael@0: #define PREF_GENERAL_LOCALE "general.useragent.locale" michael@0: #define PREF_DOWNLOAD_BLOCK_TABLE "urlclassifier.downloadBlockTable" michael@0: #define PREF_DOWNLOAD_ALLOW_TABLE "urlclassifier.downloadAllowTable" michael@0: michael@0: // NSPR_LOG_MODULES=ApplicationReputation:5 michael@0: #if defined(PR_LOGGING) michael@0: PRLogModuleInfo *ApplicationReputationService::prlog = nullptr; michael@0: #define LOG(args) PR_LOG(ApplicationReputationService::prlog, PR_LOG_DEBUG, args) michael@0: #define LOG_ENABLED() PR_LOG_TEST(ApplicationReputationService::prlog, 4) michael@0: #else michael@0: #define LOG(args) michael@0: #define LOG_ENABLED() (false) michael@0: #endif michael@0: michael@0: class PendingDBLookup; michael@0: michael@0: // A single use class private to ApplicationReputationService encapsulating an michael@0: // nsIApplicationReputationQuery and an nsIApplicationReputationCallback. Once michael@0: // created by ApplicationReputationService, it is guaranteed to call mCallback. michael@0: // This class is private to ApplicationReputationService. michael@0: class PendingLookup MOZ_FINAL : public nsIStreamListener michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: NS_DECL_NSIREQUESTOBSERVER michael@0: NS_DECL_NSISTREAMLISTENER michael@0: michael@0: // Constructor and destructor. michael@0: PendingLookup(nsIApplicationReputationQuery* aQuery, michael@0: nsIApplicationReputationCallback* aCallback); michael@0: ~PendingLookup(); michael@0: michael@0: // Start the lookup. The lookup may have 2 parts: local and remote. In the michael@0: // local lookup, PendingDBLookups are created to query the local allow and michael@0: // blocklists for various URIs associated with this downloaded file. In the michael@0: // event that no results are found, a remote lookup is sent to the Application michael@0: // Reputation server. michael@0: nsresult StartLookup(); michael@0: michael@0: private: michael@0: friend class PendingDBLookup; michael@0: michael@0: // Telemetry states. michael@0: // Status of the remote response (valid or not). michael@0: enum SERVER_RESPONSE_TYPES { michael@0: SERVER_RESPONSE_VALID = 0, michael@0: SERVER_RESPONSE_FAILED = 1, michael@0: SERVER_RESPONSE_INVALID = 2, michael@0: }; michael@0: michael@0: // The query containing metadata about the downloaded file. michael@0: nsCOMPtr mQuery; michael@0: michael@0: // The callback with which to report the verdict. michael@0: nsCOMPtr mCallback; michael@0: michael@0: // An array of strings created from certificate information used to whitelist michael@0: // the downloaded file. michael@0: nsTArray mAllowlistSpecs; michael@0: // The source URI of the download, the referrer and possibly any redirects. michael@0: nsTArray mAnylistSpecs; michael@0: michael@0: // When we started this query michael@0: TimeStamp mStartTime; michael@0: michael@0: // The protocol buffer used to store signature information extracted using michael@0: // the Windows Authenticode API, if the binary is signed. michael@0: ClientDownloadRequest_SignatureInfo mSignatureInfo; michael@0: michael@0: // The response from the application reputation query. This is read in chunks michael@0: // as part of our nsIStreamListener implementation and may contain embedded michael@0: // NULLs. michael@0: nsCString mResponse; michael@0: michael@0: // Returns true if the file is likely to be binary on Windows. michael@0: bool IsBinaryFile(); michael@0: michael@0: // Clean up and call the callback. PendingLookup must not be used after this michael@0: // function is called. michael@0: nsresult OnComplete(bool shouldBlock, nsresult rv); michael@0: michael@0: // Wrapper function for nsIStreamListener.onStopRequest to make it easy to michael@0: // guarantee calling the callback michael@0: nsresult OnStopRequestInternal(nsIRequest *aRequest, michael@0: nsISupports *aContext, michael@0: nsresult aResult, michael@0: bool* aShouldBlock); michael@0: michael@0: // Escape '/' and '%' in certificate attribute values. michael@0: nsCString EscapeCertificateAttribute(const nsACString& aAttribute); michael@0: michael@0: // Escape ':' in fingerprint values. michael@0: nsCString EscapeFingerprint(const nsACString& aAttribute); michael@0: michael@0: // Generate whitelist strings for the given certificate pair from the same michael@0: // certificate chain. michael@0: nsresult GenerateWhitelistStringsForPair( michael@0: nsIX509Cert* certificate, nsIX509Cert* issuer); michael@0: michael@0: // Generate whitelist strings for the given certificate chain, which starts michael@0: // with the signer and may go all the way to the root cert. michael@0: nsresult GenerateWhitelistStringsForChain( michael@0: const ClientDownloadRequest_CertificateChain& aChain); michael@0: michael@0: // For signed binaries, generate strings of the form: michael@0: // http://sb-ssl.google.com/safebrowsing/csd/certificate/ michael@0: // [/CN=][/O=][/OU=] michael@0: // for each (cert, issuer) pair in each chain of certificates that is michael@0: // associated with the binary. michael@0: nsresult GenerateWhitelistStrings( michael@0: const ClientDownloadRequest_SignatureInfo& aSignatureInfo); michael@0: michael@0: // Parse the XPCOM certificate lists and stick them into the protocol buffer michael@0: // version. michael@0: nsresult ParseCertificates(nsIArray* aSigArray, michael@0: ClientDownloadRequest_SignatureInfo* aSigInfo); michael@0: michael@0: // Helper function to ensure that we call PendingLookup::LookupNext or michael@0: // PendingLookup::OnComplete. michael@0: nsresult DoLookupInternal(); michael@0: michael@0: // Looks up all the URIs that may be responsible for allowlisting or michael@0: // blocklisting the downloaded file. These URIs may include whitelist strings michael@0: // generated by certificates verifying the binary as well as the target URI michael@0: // from which the file was downloaded. michael@0: nsresult LookupNext(); michael@0: michael@0: // Sends a query to the remote application reputation service. Returns NS_OK michael@0: // on success. michael@0: nsresult SendRemoteQuery(); michael@0: michael@0: // Helper function to ensure that we always call the callback. michael@0: nsresult SendRemoteQueryInternal(); michael@0: }; michael@0: michael@0: // A single-use class for looking up a single URI in the safebrowsing DB. This michael@0: // class is private to PendingLookup. michael@0: class PendingDBLookup MOZ_FINAL : public nsIUrlClassifierCallback michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: NS_DECL_NSIURLCLASSIFIERCALLBACK michael@0: michael@0: // Constructor and destructor michael@0: PendingDBLookup(PendingLookup* aPendingLookup); michael@0: ~PendingDBLookup(); michael@0: michael@0: // Look up the given URI in the safebrowsing DBs, optionally on both the allow michael@0: // list and the blocklist. If there is a match, call michael@0: // PendingLookup::OnComplete. Otherwise, call PendingLookup::LookupNext. michael@0: nsresult LookupSpec(const nsACString& aSpec, bool aAllowlistOnly); michael@0: private: michael@0: // The download appeared on the allowlist, blocklist, or no list (and thus michael@0: // could trigger a remote query. michael@0: enum LIST_TYPES { michael@0: ALLOW_LIST = 0, michael@0: BLOCK_LIST = 1, michael@0: NO_LIST = 2, michael@0: }; michael@0: michael@0: nsCString mSpec; michael@0: bool mAllowlistOnly; michael@0: nsRefPtr mPendingLookup; michael@0: nsresult LookupSpecInternal(const nsACString& aSpec); michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(PendingDBLookup, michael@0: nsIUrlClassifierCallback) michael@0: michael@0: PendingDBLookup::PendingDBLookup(PendingLookup* aPendingLookup) : michael@0: mAllowlistOnly(false), michael@0: mPendingLookup(aPendingLookup) michael@0: { michael@0: LOG(("Created pending DB lookup [this = %p]", this)); michael@0: } michael@0: michael@0: PendingDBLookup::~PendingDBLookup() michael@0: { michael@0: LOG(("Destroying pending DB lookup [this = %p]", this)); michael@0: mPendingLookup = nullptr; michael@0: } michael@0: michael@0: nsresult michael@0: PendingDBLookup::LookupSpec(const nsACString& aSpec, michael@0: bool aAllowlistOnly) michael@0: { michael@0: LOG(("Checking principal %s", aSpec.Data())); michael@0: mSpec = aSpec; michael@0: mAllowlistOnly = aAllowlistOnly; michael@0: nsresult rv = LookupSpecInternal(aSpec); michael@0: if (NS_FAILED(rv)) { michael@0: LOG(("Error in LookupSpecInternal")); michael@0: return mPendingLookup->OnComplete(false, NS_OK); michael@0: } michael@0: // LookupSpecInternal has called nsIUrlClassifierCallback.lookup, which is michael@0: // guaranteed to call HandleEvent. michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: PendingDBLookup::LookupSpecInternal(const nsACString& aSpec) michael@0: { michael@0: nsresult rv; michael@0: michael@0: nsCOMPtr uri; michael@0: nsCOMPtr ios = do_GetService(NS_IOSERVICE_CONTRACTID, &rv); michael@0: rv = ios->NewURI(aSpec, nullptr, nullptr, getter_AddRefs(uri)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr principal; michael@0: nsCOMPtr secMan = michael@0: do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = secMan->GetNoAppCodebasePrincipal(uri, getter_AddRefs(principal)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Check local lists to see if the URI has already been whitelisted or michael@0: // blacklisted. michael@0: LOG(("Checking DB service for principal %s [this = %p]", mSpec.get(), this)); michael@0: nsCOMPtr dbService = michael@0: do_GetService(NS_URLCLASSIFIERDBSERVICE_CONTRACTID, &rv); michael@0: nsAutoCString tables; michael@0: nsAutoCString allowlist; michael@0: Preferences::GetCString(PREF_DOWNLOAD_ALLOW_TABLE, &allowlist); michael@0: if (!allowlist.IsEmpty()) { michael@0: tables.Append(allowlist); michael@0: } michael@0: nsAutoCString blocklist; michael@0: Preferences::GetCString(PREF_DOWNLOAD_BLOCK_TABLE, &blocklist); michael@0: if (!mAllowlistOnly && !blocklist.IsEmpty()) { michael@0: tables.Append(","); michael@0: tables.Append(blocklist); michael@0: } michael@0: return dbService->Lookup(principal, tables, this); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: PendingDBLookup::HandleEvent(const nsACString& tables) michael@0: { michael@0: // HandleEvent is guaranteed to call either: michael@0: // 1) PendingLookup::OnComplete if the URL can be classified locally, or michael@0: // 2) PendingLookup::LookupNext if the URL can be cannot classified locally. michael@0: // Blocklisting trumps allowlisting. michael@0: nsAutoCString blockList; michael@0: Preferences::GetCString(PREF_DOWNLOAD_BLOCK_TABLE, &blockList); michael@0: if (!mAllowlistOnly && FindInReadable(blockList, tables)) { michael@0: Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_LOCAL, BLOCK_LIST); michael@0: LOG(("Found principal %s on blocklist [this = %p]", mSpec.get(), this)); michael@0: return mPendingLookup->OnComplete(true, NS_OK); michael@0: } michael@0: michael@0: nsAutoCString allowList; michael@0: Preferences::GetCString(PREF_DOWNLOAD_ALLOW_TABLE, &allowList); michael@0: if (FindInReadable(allowList, tables)) { michael@0: Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_LOCAL, ALLOW_LIST); michael@0: LOG(("Found principal %s on allowlist [this = %p]", mSpec.get(), this)); michael@0: return mPendingLookup->OnComplete(false, NS_OK); michael@0: } michael@0: michael@0: LOG(("Didn't find principal %s on any list [this = %p]", mSpec.get(), this)); michael@0: Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_LOCAL, NO_LIST); michael@0: return mPendingLookup->LookupNext(); michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(PendingLookup, michael@0: nsIStreamListener, michael@0: nsIRequestObserver) michael@0: michael@0: PendingLookup::PendingLookup(nsIApplicationReputationQuery* aQuery, michael@0: nsIApplicationReputationCallback* aCallback) : michael@0: mQuery(aQuery), michael@0: mCallback(aCallback) michael@0: { michael@0: LOG(("Created pending lookup [this = %p]", this)); michael@0: } michael@0: michael@0: PendingLookup::~PendingLookup() michael@0: { michael@0: LOG(("Destroying pending lookup [this = %p]", this)); michael@0: } michael@0: michael@0: bool michael@0: PendingLookup::IsBinaryFile() michael@0: { michael@0: nsString fileName; michael@0: nsresult rv = mQuery->GetSuggestedFileName(fileName); michael@0: if (NS_FAILED(rv)) { michael@0: return false; michael@0: } michael@0: return michael@0: // Executable extensions for MS Windows, from michael@0: // https://code.google.com/p/chromium/codesearch#chromium/src/chrome/common/safe_browsing/download_protection_util.cc&l=14 michael@0: StringEndsWith(fileName, NS_LITERAL_STRING(".apk")) || michael@0: StringEndsWith(fileName, NS_LITERAL_STRING(".bas")) || michael@0: StringEndsWith(fileName, NS_LITERAL_STRING(".bat")) || michael@0: StringEndsWith(fileName, NS_LITERAL_STRING(".cab")) || michael@0: StringEndsWith(fileName, NS_LITERAL_STRING(".cmd")) || michael@0: StringEndsWith(fileName, NS_LITERAL_STRING(".com")) || michael@0: StringEndsWith(fileName, NS_LITERAL_STRING(".exe")) || michael@0: StringEndsWith(fileName, NS_LITERAL_STRING(".hta")) || michael@0: StringEndsWith(fileName, NS_LITERAL_STRING(".msi")) || michael@0: StringEndsWith(fileName, NS_LITERAL_STRING(".pif")) || michael@0: StringEndsWith(fileName, NS_LITERAL_STRING(".reg")) || michael@0: StringEndsWith(fileName, NS_LITERAL_STRING(".scr")) || michael@0: StringEndsWith(fileName, NS_LITERAL_STRING(".vb")) || michael@0: StringEndsWith(fileName, NS_LITERAL_STRING(".vbs")) || michael@0: StringEndsWith(fileName, NS_LITERAL_STRING(".zip")); michael@0: } michael@0: michael@0: nsresult michael@0: PendingLookup::LookupNext() michael@0: { michael@0: // We must call LookupNext or SendRemoteQuery upon return. michael@0: // Look up all of the URLs that could whitelist this download. michael@0: // Blacklist first. michael@0: int index = mAnylistSpecs.Length() - 1; michael@0: nsCString spec; michael@0: bool allowlistOnly = false; michael@0: if (index >= 0) { michael@0: // Check the source URI and referrer. michael@0: spec = mAnylistSpecs[index]; michael@0: mAnylistSpecs.RemoveElementAt(index); michael@0: } else { michael@0: // Check the allowlists next. michael@0: index = mAllowlistSpecs.Length() - 1; michael@0: if (index >= 0) { michael@0: allowlistOnly = true; michael@0: spec = mAllowlistSpecs[index]; michael@0: mAllowlistSpecs.RemoveElementAt(index); michael@0: } michael@0: } michael@0: if (index >= 0) { michael@0: nsRefPtr lookup(new PendingDBLookup(this)); michael@0: return lookup->LookupSpec(spec, allowlistOnly); michael@0: } michael@0: #ifdef XP_WIN michael@0: // There are no more URIs to check against local list. If the file is not michael@0: // eligible for remote lookup, bail. michael@0: if (!IsBinaryFile()) { michael@0: LOG(("Not eligible for remote lookups [this=%x]", this)); michael@0: return OnComplete(false, NS_OK); michael@0: } michael@0: // Send the remote query if we are on Windows. michael@0: nsresult rv = SendRemoteQuery(); michael@0: if (NS_FAILED(rv)) { michael@0: return OnComplete(false, rv); michael@0: } michael@0: return NS_OK; michael@0: #else michael@0: return OnComplete(false, NS_OK); michael@0: #endif michael@0: } michael@0: michael@0: nsCString michael@0: PendingLookup::EscapeCertificateAttribute(const nsACString& aAttribute) michael@0: { michael@0: // Escape '/' because it's a field separator, and '%' because Chrome does michael@0: nsCString escaped; michael@0: escaped.SetCapacity(aAttribute.Length()); michael@0: for (unsigned int i = 0; i < aAttribute.Length(); ++i) { michael@0: if (aAttribute.Data()[i] == '%') { michael@0: escaped.Append("%25"); michael@0: } else if (aAttribute.Data()[i] == '/') { michael@0: escaped.Append("%2F"); michael@0: } else if (aAttribute.Data()[i] == ' ') { michael@0: escaped.Append("%20"); michael@0: } else { michael@0: escaped.Append(aAttribute.Data()[i]); michael@0: } michael@0: } michael@0: return escaped; michael@0: } michael@0: michael@0: nsCString michael@0: PendingLookup::EscapeFingerprint(const nsACString& aFingerprint) michael@0: { michael@0: // Google's fingerprint doesn't have colons michael@0: nsCString escaped; michael@0: escaped.SetCapacity(aFingerprint.Length()); michael@0: for (unsigned int i = 0; i < aFingerprint.Length(); ++i) { michael@0: if (aFingerprint.Data()[i] != ':') { michael@0: escaped.Append(aFingerprint.Data()[i]); michael@0: } michael@0: } michael@0: return escaped; michael@0: } michael@0: michael@0: nsresult michael@0: PendingLookup::GenerateWhitelistStringsForPair( michael@0: nsIX509Cert* certificate, michael@0: nsIX509Cert* issuer) michael@0: { michael@0: // The whitelist paths have format: michael@0: // http://sb-ssl.google.com/safebrowsing/csd/certificate/[/CN=][/O=][/OU=] michael@0: // Any of CN, O, or OU may be omitted from the whitelist entry. Unfortunately michael@0: // this is not publicly documented, but the Chrome implementation can be found michael@0: // here: michael@0: // https://code.google.com/p/chromium/codesearch#search/&q=GetCertificateWhitelistStrings michael@0: nsCString whitelistString( michael@0: "http://sb-ssl.google.com/safebrowsing/csd/certificate/"); michael@0: michael@0: nsString fingerprint; michael@0: nsresult rv = issuer->GetSha1Fingerprint(fingerprint); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: whitelistString.Append( michael@0: EscapeFingerprint(NS_ConvertUTF16toUTF8(fingerprint))); michael@0: michael@0: nsString commonName; michael@0: rv = certificate->GetCommonName(commonName); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (!commonName.IsEmpty()) { michael@0: whitelistString.Append("/CN="); michael@0: whitelistString.Append( michael@0: EscapeCertificateAttribute(NS_ConvertUTF16toUTF8(commonName))); michael@0: } michael@0: michael@0: nsString organization; michael@0: rv = certificate->GetOrganization(organization); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (!organization.IsEmpty()) { michael@0: whitelistString.Append("/O="); michael@0: whitelistString.Append( michael@0: EscapeCertificateAttribute(NS_ConvertUTF16toUTF8(organization))); michael@0: } michael@0: michael@0: nsString organizationalUnit; michael@0: rv = certificate->GetOrganizationalUnit(organizationalUnit); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (!organizationalUnit.IsEmpty()) { michael@0: whitelistString.Append("/OU="); michael@0: whitelistString.Append( michael@0: EscapeCertificateAttribute(NS_ConvertUTF16toUTF8(organizationalUnit))); michael@0: } michael@0: LOG(("Whitelisting %s", whitelistString.get())); michael@0: michael@0: mAllowlistSpecs.AppendElement(whitelistString); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: PendingLookup::GenerateWhitelistStringsForChain( michael@0: const safe_browsing::ClientDownloadRequest_CertificateChain& aChain) michael@0: { michael@0: // We need a signing certificate and an issuer to construct a whitelist michael@0: // entry. michael@0: if (aChain.element_size() < 2) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Get the signer. michael@0: nsresult rv; michael@0: nsCOMPtr certDB = do_GetService(NS_X509CERTDB_CONTRACTID, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr signer; michael@0: rv = certDB->ConstructX509( michael@0: const_cast(aChain.element(0).certificate().data()), michael@0: aChain.element(0).certificate().size(), getter_AddRefs(signer)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: for (int i = 1; i < aChain.element_size(); ++i) { michael@0: // Get the issuer. michael@0: nsCOMPtr issuer; michael@0: rv = certDB->ConstructX509( michael@0: const_cast(aChain.element(i).certificate().data()), michael@0: aChain.element(i).certificate().size(), getter_AddRefs(issuer)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsresult rv = GenerateWhitelistStringsForPair(signer, issuer); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: PendingLookup::GenerateWhitelistStrings( michael@0: const safe_browsing::ClientDownloadRequest_SignatureInfo& aSignatureInfo) michael@0: { michael@0: for (int i = 0; i < aSignatureInfo.certificate_chain_size(); ++i) { michael@0: nsresult rv = GenerateWhitelistStringsForChain( michael@0: aSignatureInfo.certificate_chain(i)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: PendingLookup::StartLookup() michael@0: { michael@0: mStartTime = TimeStamp::Now(); michael@0: nsresult rv = DoLookupInternal(); michael@0: if (NS_FAILED(rv)) { michael@0: return OnComplete(false, NS_OK); michael@0: }; michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: PendingLookup::DoLookupInternal() michael@0: { michael@0: // We want to check the target URI against the local lists. michael@0: nsCOMPtr uri; michael@0: nsresult rv = mQuery->GetSourceURI(getter_AddRefs(uri)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCString spec; michael@0: rv = uri->GetSpec(spec); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: mAnylistSpecs.AppendElement(spec); michael@0: michael@0: nsCOMPtr referrer = nullptr; michael@0: rv = mQuery->GetReferrerURI(getter_AddRefs(referrer)); michael@0: if (referrer) { michael@0: nsCString spec; michael@0: rv = referrer->GetSpec(spec); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: mAnylistSpecs.AppendElement(spec); michael@0: } michael@0: michael@0: // Extract the signature and parse certificates so we can use it to check michael@0: // whitelists. michael@0: nsCOMPtr sigArray; michael@0: rv = mQuery->GetSignatureInfo(getter_AddRefs(sigArray)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (sigArray) { michael@0: rv = ParseCertificates(sigArray, &mSignatureInfo); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: rv = GenerateWhitelistStrings(mSignatureInfo); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Start the call chain. michael@0: return LookupNext(); michael@0: } michael@0: michael@0: nsresult michael@0: PendingLookup::OnComplete(bool shouldBlock, nsresult rv) michael@0: { michael@0: Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_SHOULD_BLOCK, michael@0: shouldBlock); michael@0: #if defined(PR_LOGGING) michael@0: double t = (TimeStamp::Now() - mStartTime).ToMilliseconds(); michael@0: #endif michael@0: if (shouldBlock) { michael@0: LOG(("Application Reputation check failed, blocking bad binary in %f ms " michael@0: "[this = %p]", t, this)); michael@0: } else { michael@0: LOG(("Application Reputation check passed in %f ms [this = %p]", t, this)); michael@0: } michael@0: nsresult res = mCallback->OnComplete(shouldBlock, rv); michael@0: return res; michael@0: } michael@0: michael@0: nsresult michael@0: PendingLookup::ParseCertificates( michael@0: nsIArray* aSigArray, michael@0: ClientDownloadRequest_SignatureInfo* aSignatureInfo) michael@0: { michael@0: // If we haven't been set for any reason, bail. michael@0: NS_ENSURE_ARG_POINTER(aSigArray); michael@0: michael@0: // Binaries may be signed by multiple chains of certificates. If there are no michael@0: // chains, the binary is unsigned (or we were unable to extract signature michael@0: // information on a non-Windows platform) michael@0: nsCOMPtr chains; michael@0: nsresult rv = aSigArray->Enumerate(getter_AddRefs(chains)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool hasMoreChains = false; michael@0: rv = chains->HasMoreElements(&hasMoreChains); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: while (hasMoreChains) { michael@0: nsCOMPtr supports; michael@0: rv = chains->GetNext(getter_AddRefs(supports)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr certList = do_QueryInterface(supports, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: safe_browsing::ClientDownloadRequest_CertificateChain* certChain = michael@0: aSignatureInfo->add_certificate_chain(); michael@0: nsCOMPtr chainElt; michael@0: rv = certList->GetEnumerator(getter_AddRefs(chainElt)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Each chain may have multiple certificates. michael@0: bool hasMoreCerts = false; michael@0: rv = chainElt->HasMoreElements(&hasMoreCerts); michael@0: while (hasMoreCerts) { michael@0: nsCOMPtr supports; michael@0: rv = chainElt->GetNext(getter_AddRefs(supports)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr cert = do_QueryInterface(supports, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: uint8_t* data = nullptr; michael@0: uint32_t len = 0; michael@0: rv = cert->GetRawDER(&len, &data); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Add this certificate to the protobuf to send remotely. michael@0: certChain->add_element()->set_certificate(data, len); michael@0: nsMemory::Free(data); michael@0: michael@0: rv = chainElt->HasMoreElements(&hasMoreCerts); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: rv = chains->HasMoreElements(&hasMoreChains); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: if (aSignatureInfo->certificate_chain_size() > 0) { michael@0: aSignatureInfo->set_trusted(true); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: PendingLookup::SendRemoteQuery() michael@0: { michael@0: nsresult rv = SendRemoteQueryInternal(); michael@0: if (NS_FAILED(rv)) { michael@0: return OnComplete(false, NS_OK); michael@0: } michael@0: // SendRemoteQueryInternal has fired off the query and we call OnComplete in michael@0: // the nsIStreamListener.onStopRequest. michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: PendingLookup::SendRemoteQueryInternal() michael@0: { michael@0: LOG(("Sending remote query for application reputation [this = %p]", this)); michael@0: // We did not find a local result, so fire off the query to the application michael@0: // reputation service. michael@0: safe_browsing::ClientDownloadRequest req; michael@0: nsCOMPtr uri; michael@0: nsresult rv; michael@0: rv = mQuery->GetSourceURI(getter_AddRefs(uri)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: nsCString spec; michael@0: rv = uri->GetSpec(spec); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: req.set_url(spec.get()); michael@0: michael@0: uint32_t fileSize; michael@0: rv = mQuery->GetFileSize(&fileSize); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: req.set_length(fileSize); michael@0: // We have no way of knowing whether or not a user initiated the download. michael@0: req.set_user_initiated(false); michael@0: michael@0: nsCString locale; michael@0: NS_ENSURE_SUCCESS(Preferences::GetCString(PREF_GENERAL_LOCALE, &locale), michael@0: NS_ERROR_NOT_AVAILABLE); michael@0: req.set_locale(locale.get()); michael@0: nsCString sha256Hash; michael@0: rv = mQuery->GetSha256Hash(sha256Hash); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: req.mutable_digests()->set_sha256(sha256Hash.Data()); michael@0: nsString fileName; michael@0: rv = mQuery->GetSuggestedFileName(fileName); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: req.set_file_basename(NS_ConvertUTF16toUTF8(fileName).get()); michael@0: req.mutable_signature()->CopyFrom(mSignatureInfo); michael@0: michael@0: if (req.signature().trusted()) { michael@0: LOG(("Got signed binary for remote application reputation check " michael@0: "[this = %p]", this)); michael@0: } else { michael@0: LOG(("Got unsigned binary for remote application reputation check " michael@0: "[this = %p]", this)); michael@0: } michael@0: michael@0: // Serialize the protocol buffer to a string. This can only fail if we are michael@0: // out of memory, or if the protocol buffer req is missing required fields michael@0: // (only the URL for now). michael@0: std::string serialized; michael@0: if (!req.SerializeToString(&serialized)) { michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: // Set the input stream to the serialized protocol buffer michael@0: nsCOMPtr sstream = michael@0: do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = sstream->SetData(serialized.c_str(), serialized.length()); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Set up the channel to transmit the request to the service. michael@0: nsCOMPtr channel; michael@0: nsCString serviceUrl; michael@0: NS_ENSURE_SUCCESS(Preferences::GetCString(PREF_SB_APP_REP_URL, &serviceUrl), michael@0: NS_ERROR_NOT_AVAILABLE); michael@0: nsCOMPtr ios = do_GetService(NS_IOSERVICE_CONTRACTID, &rv); michael@0: rv = ios->NewChannel(serviceUrl, nullptr, nullptr, getter_AddRefs(channel)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr httpChannel(do_QueryInterface(channel, &rv)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Upload the protobuf to the application reputation service. michael@0: nsCOMPtr uploadChannel = do_QueryInterface(channel, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = uploadChannel->ExplicitSetUploadStream(sstream, michael@0: NS_LITERAL_CSTRING("application/octet-stream"), serialized.size(), michael@0: NS_LITERAL_CSTRING("POST"), false); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Set the Safebrowsing cookie jar, so that the regular Google cookie is not michael@0: // sent with this request. See bug 897516. michael@0: nsCOMPtr loadContext = michael@0: new mozilla::LoadContext(NECKO_SAFEBROWSING_APP_ID); michael@0: rv = channel->SetNotificationCallbacks(loadContext); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = channel->AsyncOpen(this, nullptr); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// nsIStreamListener michael@0: static NS_METHOD michael@0: AppendSegmentToString(nsIInputStream* inputStream, michael@0: void *closure, michael@0: const char *rawSegment, michael@0: uint32_t toOffset, michael@0: uint32_t count, michael@0: uint32_t *writeCount) { michael@0: nsAutoCString* decodedData = static_cast(closure); michael@0: decodedData->Append(rawSegment, count); michael@0: *writeCount = count; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: PendingLookup::OnDataAvailable(nsIRequest *aRequest, michael@0: nsISupports *aContext, michael@0: nsIInputStream *aStream, michael@0: uint64_t offset, michael@0: uint32_t count) { michael@0: uint32_t read; michael@0: return aStream->ReadSegments(AppendSegmentToString, &mResponse, count, &read); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: PendingLookup::OnStartRequest(nsIRequest *aRequest, michael@0: nsISupports *aContext) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: PendingLookup::OnStopRequest(nsIRequest *aRequest, michael@0: nsISupports *aContext, michael@0: nsresult aResult) { michael@0: NS_ENSURE_STATE(mCallback); michael@0: michael@0: bool shouldBlock = false; michael@0: nsresult rv = OnStopRequestInternal(aRequest, aContext, aResult, michael@0: &shouldBlock); michael@0: OnComplete(shouldBlock, rv); michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: PendingLookup::OnStopRequestInternal(nsIRequest *aRequest, michael@0: nsISupports *aContext, michael@0: nsresult aResult, michael@0: bool* aShouldBlock) { michael@0: if (NS_FAILED(aResult)) { michael@0: Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_SERVER, michael@0: SERVER_RESPONSE_FAILED); michael@0: return aResult; michael@0: } michael@0: michael@0: *aShouldBlock = false; michael@0: nsresult rv; michael@0: nsCOMPtr channel = do_QueryInterface(aRequest, &rv); michael@0: if (NS_FAILED(rv)) { michael@0: Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_SERVER, michael@0: SERVER_RESPONSE_FAILED); michael@0: return rv; michael@0: } michael@0: michael@0: uint32_t status = 0; michael@0: rv = channel->GetResponseStatus(&status); michael@0: if (NS_FAILED(rv)) { michael@0: Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_SERVER, michael@0: SERVER_RESPONSE_FAILED); michael@0: return rv; michael@0: } michael@0: michael@0: if (status != 200) { michael@0: Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_SERVER, michael@0: SERVER_RESPONSE_FAILED); michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: std::string buf(mResponse.Data(), mResponse.Length()); michael@0: safe_browsing::ClientDownloadResponse response; michael@0: if (!response.ParseFromString(buf)) { michael@0: NS_WARNING("Could not parse protocol buffer"); michael@0: Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_SERVER, michael@0: SERVER_RESPONSE_INVALID); michael@0: return NS_ERROR_CANNOT_CONVERT_DATA; michael@0: } michael@0: michael@0: // There are several more verdicts, but we only respect one for now and treat michael@0: // everything else as SAFE. michael@0: Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_SERVER, michael@0: SERVER_RESPONSE_VALID); michael@0: if (response.verdict() == safe_browsing::ClientDownloadResponse::DANGEROUS) { michael@0: *aShouldBlock = true; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(ApplicationReputationService, michael@0: nsIApplicationReputationService) michael@0: michael@0: ApplicationReputationService* michael@0: ApplicationReputationService::gApplicationReputationService = nullptr; michael@0: michael@0: ApplicationReputationService* michael@0: ApplicationReputationService::GetSingleton() michael@0: { michael@0: if (gApplicationReputationService) { michael@0: NS_ADDREF(gApplicationReputationService); michael@0: return gApplicationReputationService; michael@0: } michael@0: michael@0: // We're not initialized yet. michael@0: gApplicationReputationService = new ApplicationReputationService(); michael@0: if (gApplicationReputationService) { michael@0: NS_ADDREF(gApplicationReputationService); michael@0: } michael@0: michael@0: return gApplicationReputationService; michael@0: } michael@0: michael@0: ApplicationReputationService::ApplicationReputationService() michael@0: { michael@0: #if defined(PR_LOGGING) michael@0: if (!prlog) { michael@0: prlog = PR_NewLogModule("ApplicationReputation"); michael@0: } michael@0: #endif michael@0: LOG(("Application reputation service started up")); michael@0: } michael@0: michael@0: ApplicationReputationService::~ApplicationReputationService() { michael@0: LOG(("Application reputation service shutting down")); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: ApplicationReputationService::QueryReputation( michael@0: nsIApplicationReputationQuery* aQuery, michael@0: nsIApplicationReputationCallback* aCallback) { michael@0: NS_ENSURE_ARG_POINTER(aQuery); michael@0: NS_ENSURE_ARG_POINTER(aCallback); michael@0: michael@0: Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_COUNT, true); michael@0: nsresult rv = QueryReputationInternal(aQuery, aCallback); michael@0: if (NS_FAILED(rv)) { michael@0: Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_SHOULD_BLOCK, michael@0: false); michael@0: aCallback->OnComplete(false, rv); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult ApplicationReputationService::QueryReputationInternal( michael@0: nsIApplicationReputationQuery* aQuery, michael@0: nsIApplicationReputationCallback* aCallback) { michael@0: nsresult rv; michael@0: // If malware checks aren't enabled, don't query application reputation. michael@0: if (!Preferences::GetBool(PREF_SB_MALWARE_ENABLED, false)) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: // If there is no service URL for querying application reputation, abort. michael@0: nsCString serviceUrl; michael@0: NS_ENSURE_SUCCESS(Preferences::GetCString(PREF_SB_APP_REP_URL, &serviceUrl), michael@0: NS_ERROR_NOT_AVAILABLE); michael@0: if (serviceUrl.EqualsLiteral("")) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: nsCOMPtr uri; michael@0: rv = aQuery->GetSourceURI(getter_AddRefs(uri)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: // Bail if the URI hasn't been set. michael@0: NS_ENSURE_STATE(uri); michael@0: michael@0: // Create a new pending lookup and start the call chain. michael@0: nsRefPtr lookup(new PendingLookup(aQuery, aCallback)); michael@0: NS_ENSURE_STATE(lookup); michael@0: michael@0: return lookup->StartLookup(); michael@0: }