toolkit/components/downloads/ApplicationReputation.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* vim: set ts=2 et sw=2 tw=80: */
     3 /* This Source Code Form is subject to the terms of the Mozilla Public
     4  * License, v. 2.0. If a copy of the MPL was not distributed with this
     5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 // See
     7 // https://wiki.mozilla.org/Security/Features/Application_Reputation_Design_Doc
     8 // for a description of Chrome's implementation of this feature.
     9 #include "ApplicationReputation.h"
    10 #include "csd.pb.h"
    12 #include "nsIArray.h"
    13 #include "nsIApplicationReputation.h"
    14 #include "nsIChannel.h"
    15 #include "nsIHttpChannel.h"
    16 #include "nsIIOService.h"
    17 #include "nsIPrefService.h"
    18 #include "nsIScriptSecurityManager.h"
    19 #include "nsIStreamListener.h"
    20 #include "nsIStringStream.h"
    21 #include "nsIUploadChannel2.h"
    22 #include "nsIURI.h"
    23 #include "nsIUrlClassifierDBService.h"
    24 #include "nsIX509Cert.h"
    25 #include "nsIX509CertDB.h"
    26 #include "nsIX509CertList.h"
    28 #include "mozilla/Preferences.h"
    29 #include "mozilla/Services.h"
    30 #include "mozilla/Telemetry.h"
    31 #include "mozilla/TimeStamp.h"
    32 #include "mozilla/LoadContext.h"
    34 #include "nsAutoPtr.h"
    35 #include "nsCOMPtr.h"
    36 #include "nsDebug.h"
    37 #include "nsError.h"
    38 #include "nsNetCID.h"
    39 #include "nsReadableUtils.h"
    40 #include "nsServiceManagerUtils.h"
    41 #include "nsString.h"
    42 #include "nsTArray.h"
    43 #include "nsThreadUtils.h"
    44 #include "nsXPCOMStrings.h"
    46 using mozilla::Preferences;
    47 using mozilla::TimeStamp;
    48 using mozilla::Telemetry::Accumulate;
    49 using safe_browsing::ClientDownloadRequest;
    50 using safe_browsing::ClientDownloadRequest_SignatureInfo;
    51 using safe_browsing::ClientDownloadRequest_CertificateChain;
    53 // Preferences that we need to initialize the query.
    54 #define PREF_SB_APP_REP_URL "browser.safebrowsing.appRepURL"
    55 #define PREF_SB_MALWARE_ENABLED "browser.safebrowsing.malware.enabled"
    56 #define PREF_GENERAL_LOCALE "general.useragent.locale"
    57 #define PREF_DOWNLOAD_BLOCK_TABLE "urlclassifier.downloadBlockTable"
    58 #define PREF_DOWNLOAD_ALLOW_TABLE "urlclassifier.downloadAllowTable"
    60 // NSPR_LOG_MODULES=ApplicationReputation:5
    61 #if defined(PR_LOGGING)
    62 PRLogModuleInfo *ApplicationReputationService::prlog = nullptr;
    63 #define LOG(args) PR_LOG(ApplicationReputationService::prlog, PR_LOG_DEBUG, args)
    64 #define LOG_ENABLED() PR_LOG_TEST(ApplicationReputationService::prlog, 4)
    65 #else
    66 #define LOG(args)
    67 #define LOG_ENABLED() (false)
    68 #endif
    70 class PendingDBLookup;
    72 // A single use class private to ApplicationReputationService encapsulating an
    73 // nsIApplicationReputationQuery and an nsIApplicationReputationCallback. Once
    74 // created by ApplicationReputationService, it is guaranteed to call mCallback.
    75 // This class is private to ApplicationReputationService.
    76 class PendingLookup MOZ_FINAL : public nsIStreamListener
    77 {
    78 public:
    79   NS_DECL_ISUPPORTS
    80   NS_DECL_NSIREQUESTOBSERVER
    81   NS_DECL_NSISTREAMLISTENER
    83   // Constructor and destructor.
    84   PendingLookup(nsIApplicationReputationQuery* aQuery,
    85                 nsIApplicationReputationCallback* aCallback);
    86   ~PendingLookup();
    88   // Start the lookup. The lookup may have 2 parts: local and remote. In the
    89   // local lookup, PendingDBLookups are created to query the local allow and
    90   // blocklists for various URIs associated with this downloaded file. In the
    91   // event that no results are found, a remote lookup is sent to the Application
    92   // Reputation server.
    93   nsresult StartLookup();
    95 private:
    96   friend class PendingDBLookup;
    98   // Telemetry states.
    99   // Status of the remote response (valid or not).
   100   enum SERVER_RESPONSE_TYPES {
   101     SERVER_RESPONSE_VALID = 0,
   102     SERVER_RESPONSE_FAILED = 1,
   103     SERVER_RESPONSE_INVALID = 2,
   104   };
   106   // The query containing metadata about the downloaded file.
   107   nsCOMPtr<nsIApplicationReputationQuery> mQuery;
   109   // The callback with which to report the verdict.
   110   nsCOMPtr<nsIApplicationReputationCallback> mCallback;
   112   // An array of strings created from certificate information used to whitelist
   113   // the downloaded file.
   114   nsTArray<nsCString> mAllowlistSpecs;
   115   // The source URI of the download, the referrer and possibly any redirects.
   116   nsTArray<nsCString> mAnylistSpecs;
   118   // When we started this query
   119   TimeStamp mStartTime;
   121   // The protocol buffer used to store signature information extracted using
   122   // the Windows Authenticode API, if the binary is signed.
   123   ClientDownloadRequest_SignatureInfo mSignatureInfo;
   125   // The response from the application reputation query. This is read in chunks
   126   // as part of our nsIStreamListener implementation and may contain embedded
   127   // NULLs.
   128   nsCString mResponse;
   130   // Returns true if the file is likely to be binary on Windows.
   131   bool IsBinaryFile();
   133   // Clean up and call the callback. PendingLookup must not be used after this
   134   // function is called.
   135   nsresult OnComplete(bool shouldBlock, nsresult rv);
   137   // Wrapper function for nsIStreamListener.onStopRequest to make it easy to
   138   // guarantee calling the callback
   139   nsresult OnStopRequestInternal(nsIRequest *aRequest,
   140                                  nsISupports *aContext,
   141                                  nsresult aResult,
   142                                  bool* aShouldBlock);
   144   // Escape '/' and '%' in certificate attribute values.
   145   nsCString EscapeCertificateAttribute(const nsACString& aAttribute);
   147   // Escape ':' in fingerprint values.
   148   nsCString EscapeFingerprint(const nsACString& aAttribute);
   150   // Generate whitelist strings for the given certificate pair from the same
   151   // certificate chain.
   152   nsresult GenerateWhitelistStringsForPair(
   153     nsIX509Cert* certificate, nsIX509Cert* issuer);
   155   // Generate whitelist strings for the given certificate chain, which starts
   156   // with the signer and may go all the way to the root cert.
   157   nsresult GenerateWhitelistStringsForChain(
   158     const ClientDownloadRequest_CertificateChain& aChain);
   160   // For signed binaries, generate strings of the form:
   161   // http://sb-ssl.google.com/safebrowsing/csd/certificate/
   162   //   <issuer_cert_sha1_fingerprint>[/CN=<cn>][/O=<org>][/OU=<unit>]
   163   // for each (cert, issuer) pair in each chain of certificates that is
   164   // associated with the binary.
   165   nsresult GenerateWhitelistStrings(
   166     const ClientDownloadRequest_SignatureInfo& aSignatureInfo);
   168   // Parse the XPCOM certificate lists and stick them into the protocol buffer
   169   // version.
   170   nsresult ParseCertificates(nsIArray* aSigArray,
   171                              ClientDownloadRequest_SignatureInfo* aSigInfo);
   173   // Helper function to ensure that we call PendingLookup::LookupNext or
   174   // PendingLookup::OnComplete.
   175   nsresult DoLookupInternal();
   177   // Looks up all the URIs that may be responsible for allowlisting or
   178   // blocklisting the downloaded file. These URIs may include whitelist strings
   179   // generated by certificates verifying the binary as well as the target URI
   180   // from which the file was downloaded.
   181   nsresult LookupNext();
   183   // Sends a query to the remote application reputation service. Returns NS_OK
   184   // on success.
   185   nsresult SendRemoteQuery();
   187   // Helper function to ensure that we always call the callback.
   188   nsresult SendRemoteQueryInternal();
   189 };
   191 // A single-use class for looking up a single URI in the safebrowsing DB. This
   192 // class is private to PendingLookup.
   193 class PendingDBLookup MOZ_FINAL : public nsIUrlClassifierCallback
   194 {
   195 public:
   196   NS_DECL_ISUPPORTS
   197   NS_DECL_NSIURLCLASSIFIERCALLBACK
   199   // Constructor and destructor
   200   PendingDBLookup(PendingLookup* aPendingLookup);
   201   ~PendingDBLookup();
   203   // Look up the given URI in the safebrowsing DBs, optionally on both the allow
   204   // list and the blocklist. If there is a match, call
   205   // PendingLookup::OnComplete. Otherwise, call PendingLookup::LookupNext.
   206   nsresult LookupSpec(const nsACString& aSpec, bool aAllowlistOnly);
   207 private:
   208   // The download appeared on the allowlist, blocklist, or no list (and thus
   209   // could trigger a remote query.
   210   enum LIST_TYPES {
   211     ALLOW_LIST = 0,
   212     BLOCK_LIST = 1,
   213     NO_LIST = 2,
   214   };
   216   nsCString mSpec;
   217   bool mAllowlistOnly;
   218   nsRefPtr<PendingLookup> mPendingLookup;
   219   nsresult LookupSpecInternal(const nsACString& aSpec);
   220 };
   222 NS_IMPL_ISUPPORTS(PendingDBLookup,
   223                   nsIUrlClassifierCallback)
   225 PendingDBLookup::PendingDBLookup(PendingLookup* aPendingLookup) :
   226   mAllowlistOnly(false),
   227   mPendingLookup(aPendingLookup)
   228 {
   229   LOG(("Created pending DB lookup [this = %p]", this));
   230 }
   232 PendingDBLookup::~PendingDBLookup()
   233 {
   234   LOG(("Destroying pending DB lookup [this = %p]", this));
   235   mPendingLookup = nullptr;
   236 }
   238 nsresult
   239 PendingDBLookup::LookupSpec(const nsACString& aSpec,
   240                             bool aAllowlistOnly)
   241 {
   242   LOG(("Checking principal %s", aSpec.Data()));
   243   mSpec = aSpec;
   244   mAllowlistOnly = aAllowlistOnly;
   245   nsresult rv = LookupSpecInternal(aSpec);
   246   if (NS_FAILED(rv)) {
   247     LOG(("Error in LookupSpecInternal"));
   248     return mPendingLookup->OnComplete(false, NS_OK);
   249   }
   250   // LookupSpecInternal has called nsIUrlClassifierCallback.lookup, which is
   251   // guaranteed to call HandleEvent.
   252   return rv;
   253 }
   255 nsresult
   256 PendingDBLookup::LookupSpecInternal(const nsACString& aSpec)
   257 {
   258   nsresult rv;
   260   nsCOMPtr<nsIURI> uri;
   261   nsCOMPtr<nsIIOService> ios = do_GetService(NS_IOSERVICE_CONTRACTID, &rv);
   262   rv = ios->NewURI(aSpec, nullptr, nullptr, getter_AddRefs(uri));
   263   NS_ENSURE_SUCCESS(rv, rv);
   265   nsCOMPtr<nsIPrincipal> principal;
   266   nsCOMPtr<nsIScriptSecurityManager> secMan =
   267     do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
   268   NS_ENSURE_SUCCESS(rv, rv);
   270   rv = secMan->GetNoAppCodebasePrincipal(uri, getter_AddRefs(principal));
   271   NS_ENSURE_SUCCESS(rv, rv);
   273   // Check local lists to see if the URI has already been whitelisted or
   274   // blacklisted.
   275   LOG(("Checking DB service for principal %s [this = %p]", mSpec.get(), this));
   276   nsCOMPtr<nsIUrlClassifierDBService> dbService =
   277     do_GetService(NS_URLCLASSIFIERDBSERVICE_CONTRACTID, &rv);
   278   nsAutoCString tables;
   279   nsAutoCString allowlist;
   280   Preferences::GetCString(PREF_DOWNLOAD_ALLOW_TABLE, &allowlist);
   281   if (!allowlist.IsEmpty()) {
   282     tables.Append(allowlist);
   283   }
   284   nsAutoCString blocklist;
   285   Preferences::GetCString(PREF_DOWNLOAD_BLOCK_TABLE, &blocklist);
   286   if (!mAllowlistOnly && !blocklist.IsEmpty()) {
   287     tables.Append(",");
   288     tables.Append(blocklist);
   289   }
   290   return dbService->Lookup(principal, tables, this);
   291 }
   293 NS_IMETHODIMP
   294 PendingDBLookup::HandleEvent(const nsACString& tables)
   295 {
   296   // HandleEvent is guaranteed to call either:
   297   // 1) PendingLookup::OnComplete if the URL can be classified locally, or
   298   // 2) PendingLookup::LookupNext if the URL can be cannot classified locally.
   299   // Blocklisting trumps allowlisting.
   300   nsAutoCString blockList;
   301   Preferences::GetCString(PREF_DOWNLOAD_BLOCK_TABLE, &blockList);
   302   if (!mAllowlistOnly && FindInReadable(blockList, tables)) {
   303     Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_LOCAL, BLOCK_LIST);
   304     LOG(("Found principal %s on blocklist [this = %p]", mSpec.get(), this));
   305     return mPendingLookup->OnComplete(true, NS_OK);
   306   }
   308   nsAutoCString allowList;
   309   Preferences::GetCString(PREF_DOWNLOAD_ALLOW_TABLE, &allowList);
   310   if (FindInReadable(allowList, tables)) {
   311     Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_LOCAL, ALLOW_LIST);
   312     LOG(("Found principal %s on allowlist [this = %p]", mSpec.get(), this));
   313     return mPendingLookup->OnComplete(false, NS_OK);
   314   }
   316   LOG(("Didn't find principal %s on any list [this = %p]", mSpec.get(), this));
   317   Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_LOCAL, NO_LIST);
   318   return mPendingLookup->LookupNext();
   319 }
   321 NS_IMPL_ISUPPORTS(PendingLookup,
   322                   nsIStreamListener,
   323                   nsIRequestObserver)
   325 PendingLookup::PendingLookup(nsIApplicationReputationQuery* aQuery,
   326                              nsIApplicationReputationCallback* aCallback) :
   327   mQuery(aQuery),
   328   mCallback(aCallback)
   329 {
   330   LOG(("Created pending lookup [this = %p]", this));
   331 }
   333 PendingLookup::~PendingLookup()
   334 {
   335   LOG(("Destroying pending lookup [this = %p]", this));
   336 }
   338 bool
   339 PendingLookup::IsBinaryFile()
   340 {
   341   nsString fileName;
   342   nsresult rv = mQuery->GetSuggestedFileName(fileName);
   343   if (NS_FAILED(rv)) {
   344     return false;
   345   }
   346   return
   347     // Executable extensions for MS Windows, from
   348     // https://code.google.com/p/chromium/codesearch#chromium/src/chrome/common/safe_browsing/download_protection_util.cc&l=14
   349     StringEndsWith(fileName, NS_LITERAL_STRING(".apk")) ||
   350     StringEndsWith(fileName, NS_LITERAL_STRING(".bas")) ||
   351     StringEndsWith(fileName, NS_LITERAL_STRING(".bat")) ||
   352     StringEndsWith(fileName, NS_LITERAL_STRING(".cab")) ||
   353     StringEndsWith(fileName, NS_LITERAL_STRING(".cmd")) ||
   354     StringEndsWith(fileName, NS_LITERAL_STRING(".com")) ||
   355     StringEndsWith(fileName, NS_LITERAL_STRING(".exe")) ||
   356     StringEndsWith(fileName, NS_LITERAL_STRING(".hta")) ||
   357     StringEndsWith(fileName, NS_LITERAL_STRING(".msi")) ||
   358     StringEndsWith(fileName, NS_LITERAL_STRING(".pif")) ||
   359     StringEndsWith(fileName, NS_LITERAL_STRING(".reg")) ||
   360     StringEndsWith(fileName, NS_LITERAL_STRING(".scr")) ||
   361     StringEndsWith(fileName, NS_LITERAL_STRING(".vb")) ||
   362     StringEndsWith(fileName, NS_LITERAL_STRING(".vbs")) ||
   363     StringEndsWith(fileName, NS_LITERAL_STRING(".zip"));
   364 }
   366 nsresult
   367 PendingLookup::LookupNext()
   368 {
   369   // We must call LookupNext or SendRemoteQuery upon return.
   370   // Look up all of the URLs that could whitelist this download.
   371   // Blacklist first.
   372   int index = mAnylistSpecs.Length() - 1;
   373   nsCString spec;
   374   bool allowlistOnly = false;
   375   if (index >= 0) {
   376     // Check the source URI and referrer.
   377     spec = mAnylistSpecs[index];
   378     mAnylistSpecs.RemoveElementAt(index);
   379   } else {
   380     // Check the allowlists next.
   381     index = mAllowlistSpecs.Length() - 1;
   382     if (index >= 0) {
   383       allowlistOnly = true;
   384       spec = mAllowlistSpecs[index];
   385       mAllowlistSpecs.RemoveElementAt(index);
   386     }
   387   }
   388   if (index >= 0) {
   389     nsRefPtr<PendingDBLookup> lookup(new PendingDBLookup(this));
   390     return lookup->LookupSpec(spec, allowlistOnly);
   391   }
   392 #ifdef XP_WIN
   393   // There are no more URIs to check against local list. If the file is not
   394   // eligible for remote lookup, bail.
   395   if (!IsBinaryFile()) {
   396     LOG(("Not eligible for remote lookups [this=%x]", this));
   397     return OnComplete(false, NS_OK);
   398   }
   399   // Send the remote query if we are on Windows.
   400   nsresult rv = SendRemoteQuery();
   401   if (NS_FAILED(rv)) {
   402     return OnComplete(false, rv);
   403   }
   404   return NS_OK;
   405 #else
   406   return OnComplete(false, NS_OK);
   407 #endif
   408 }
   410 nsCString
   411 PendingLookup::EscapeCertificateAttribute(const nsACString& aAttribute)
   412 {
   413   // Escape '/' because it's a field separator, and '%' because Chrome does
   414   nsCString escaped;
   415   escaped.SetCapacity(aAttribute.Length());
   416   for (unsigned int i = 0; i < aAttribute.Length(); ++i) {
   417     if (aAttribute.Data()[i] == '%') {
   418       escaped.Append("%25");
   419     } else if (aAttribute.Data()[i] == '/') {
   420       escaped.Append("%2F");
   421     } else if (aAttribute.Data()[i] == ' ') {
   422       escaped.Append("%20");
   423     } else {
   424       escaped.Append(aAttribute.Data()[i]);
   425     }
   426   }
   427   return escaped;
   428 }
   430 nsCString
   431 PendingLookup::EscapeFingerprint(const nsACString& aFingerprint)
   432 {
   433   // Google's fingerprint doesn't have colons
   434   nsCString escaped;
   435   escaped.SetCapacity(aFingerprint.Length());
   436   for (unsigned int i = 0; i < aFingerprint.Length(); ++i) {
   437     if (aFingerprint.Data()[i] != ':') {
   438       escaped.Append(aFingerprint.Data()[i]);
   439     }
   440   }
   441   return escaped;
   442 }
   444 nsresult
   445 PendingLookup::GenerateWhitelistStringsForPair(
   446   nsIX509Cert* certificate,
   447   nsIX509Cert* issuer)
   448 {
   449   // The whitelist paths have format:
   450   // http://sb-ssl.google.com/safebrowsing/csd/certificate/<issuer_cert_fingerprint>[/CN=<cn>][/O=<org>][/OU=<unit>]
   451   // Any of CN, O, or OU may be omitted from the whitelist entry. Unfortunately
   452   // this is not publicly documented, but the Chrome implementation can be found
   453   // here:
   454   // https://code.google.com/p/chromium/codesearch#search/&q=GetCertificateWhitelistStrings
   455   nsCString whitelistString(
   456     "http://sb-ssl.google.com/safebrowsing/csd/certificate/");
   458   nsString fingerprint;
   459   nsresult rv = issuer->GetSha1Fingerprint(fingerprint);
   460   NS_ENSURE_SUCCESS(rv, rv);
   461   whitelistString.Append(
   462     EscapeFingerprint(NS_ConvertUTF16toUTF8(fingerprint)));
   464   nsString commonName;
   465   rv = certificate->GetCommonName(commonName);
   466   NS_ENSURE_SUCCESS(rv, rv);
   467   if (!commonName.IsEmpty()) {
   468     whitelistString.Append("/CN=");
   469     whitelistString.Append(
   470       EscapeCertificateAttribute(NS_ConvertUTF16toUTF8(commonName)));
   471   }
   473   nsString organization;
   474   rv = certificate->GetOrganization(organization);
   475   NS_ENSURE_SUCCESS(rv, rv);
   476   if (!organization.IsEmpty()) {
   477     whitelistString.Append("/O=");
   478     whitelistString.Append(
   479       EscapeCertificateAttribute(NS_ConvertUTF16toUTF8(organization)));
   480   }
   482   nsString organizationalUnit;
   483   rv = certificate->GetOrganizationalUnit(organizationalUnit);
   484   NS_ENSURE_SUCCESS(rv, rv);
   485   if (!organizationalUnit.IsEmpty()) {
   486     whitelistString.Append("/OU=");
   487     whitelistString.Append(
   488       EscapeCertificateAttribute(NS_ConvertUTF16toUTF8(organizationalUnit)));
   489   }
   490   LOG(("Whitelisting %s", whitelistString.get()));
   492   mAllowlistSpecs.AppendElement(whitelistString);
   493   return NS_OK;
   494 }
   496 nsresult
   497 PendingLookup::GenerateWhitelistStringsForChain(
   498   const safe_browsing::ClientDownloadRequest_CertificateChain& aChain)
   499 {
   500   // We need a signing certificate and an issuer to construct a whitelist
   501   // entry.
   502   if (aChain.element_size() < 2) {
   503     return NS_OK;
   504   }
   506   // Get the signer.
   507   nsresult rv;
   508   nsCOMPtr<nsIX509CertDB> certDB = do_GetService(NS_X509CERTDB_CONTRACTID, &rv);
   509   NS_ENSURE_SUCCESS(rv, rv);
   511   nsCOMPtr<nsIX509Cert> signer;
   512   rv = certDB->ConstructX509(
   513     const_cast<char *>(aChain.element(0).certificate().data()),
   514     aChain.element(0).certificate().size(), getter_AddRefs(signer));
   515   NS_ENSURE_SUCCESS(rv, rv);
   517   for (int i = 1; i < aChain.element_size(); ++i) {
   518     // Get the issuer.
   519     nsCOMPtr<nsIX509Cert> issuer;
   520     rv = certDB->ConstructX509(
   521       const_cast<char *>(aChain.element(i).certificate().data()),
   522       aChain.element(i).certificate().size(), getter_AddRefs(issuer));
   523     NS_ENSURE_SUCCESS(rv, rv);
   525     nsresult rv = GenerateWhitelistStringsForPair(signer, issuer);
   526     NS_ENSURE_SUCCESS(rv, rv);
   527   }
   528   return NS_OK;
   529 }
   531 nsresult
   532 PendingLookup::GenerateWhitelistStrings(
   533   const safe_browsing::ClientDownloadRequest_SignatureInfo& aSignatureInfo)
   534 {
   535   for (int i = 0; i < aSignatureInfo.certificate_chain_size(); ++i) {
   536     nsresult rv = GenerateWhitelistStringsForChain(
   537       aSignatureInfo.certificate_chain(i));
   538     NS_ENSURE_SUCCESS(rv, rv);
   539   }
   540   return NS_OK;
   541 }
   543 nsresult
   544 PendingLookup::StartLookup()
   545 {
   546   mStartTime = TimeStamp::Now();
   547   nsresult rv = DoLookupInternal();
   548   if (NS_FAILED(rv)) {
   549     return OnComplete(false, NS_OK);
   550   };
   551   return rv;
   552 }
   554 nsresult
   555 PendingLookup::DoLookupInternal()
   556 {
   557   // We want to check the target URI against the local lists.
   558   nsCOMPtr<nsIURI> uri;
   559   nsresult rv = mQuery->GetSourceURI(getter_AddRefs(uri));
   560   NS_ENSURE_SUCCESS(rv, rv);
   562   nsCString spec;
   563   rv = uri->GetSpec(spec);
   564   NS_ENSURE_SUCCESS(rv, rv);
   565   mAnylistSpecs.AppendElement(spec);
   567   nsCOMPtr<nsIURI> referrer = nullptr;
   568   rv = mQuery->GetReferrerURI(getter_AddRefs(referrer));
   569   if (referrer) {
   570     nsCString spec;
   571     rv = referrer->GetSpec(spec);
   572     NS_ENSURE_SUCCESS(rv, rv);
   573     mAnylistSpecs.AppendElement(spec);
   574   }
   576   // Extract the signature and parse certificates so we can use it to check
   577   // whitelists.
   578   nsCOMPtr<nsIArray> sigArray;
   579   rv = mQuery->GetSignatureInfo(getter_AddRefs(sigArray));
   580   NS_ENSURE_SUCCESS(rv, rv);
   582   if (sigArray) {
   583     rv = ParseCertificates(sigArray, &mSignatureInfo);
   584     NS_ENSURE_SUCCESS(rv, rv);
   585   }
   587   rv = GenerateWhitelistStrings(mSignatureInfo);
   588   NS_ENSURE_SUCCESS(rv, rv);
   590   // Start the call chain.
   591   return LookupNext();
   592 }
   594 nsresult
   595 PendingLookup::OnComplete(bool shouldBlock, nsresult rv)
   596 {
   597   Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_SHOULD_BLOCK,
   598     shouldBlock);
   599 #if defined(PR_LOGGING)
   600   double t = (TimeStamp::Now() - mStartTime).ToMilliseconds();
   601 #endif
   602   if (shouldBlock) {
   603     LOG(("Application Reputation check failed, blocking bad binary in %f ms "
   604          "[this = %p]", t, this));
   605   } else {
   606     LOG(("Application Reputation check passed in %f ms [this = %p]", t, this));
   607   }
   608   nsresult res = mCallback->OnComplete(shouldBlock, rv);
   609   return res;
   610 }
   612 nsresult
   613 PendingLookup::ParseCertificates(
   614   nsIArray* aSigArray,
   615   ClientDownloadRequest_SignatureInfo* aSignatureInfo)
   616 {
   617   // If we haven't been set for any reason, bail.
   618   NS_ENSURE_ARG_POINTER(aSigArray);
   620   // Binaries may be signed by multiple chains of certificates. If there are no
   621   // chains, the binary is unsigned (or we were unable to extract signature
   622   // information on a non-Windows platform)
   623   nsCOMPtr<nsISimpleEnumerator> chains;
   624   nsresult rv = aSigArray->Enumerate(getter_AddRefs(chains));
   625   NS_ENSURE_SUCCESS(rv, rv);
   627   bool hasMoreChains = false;
   628   rv = chains->HasMoreElements(&hasMoreChains);
   629   NS_ENSURE_SUCCESS(rv, rv);
   631   while (hasMoreChains) {
   632     nsCOMPtr<nsISupports> supports;
   633     rv = chains->GetNext(getter_AddRefs(supports));
   634     NS_ENSURE_SUCCESS(rv, rv);
   636     nsCOMPtr<nsIX509CertList> certList = do_QueryInterface(supports, &rv);
   637     NS_ENSURE_SUCCESS(rv, rv);
   639     safe_browsing::ClientDownloadRequest_CertificateChain* certChain =
   640       aSignatureInfo->add_certificate_chain();
   641     nsCOMPtr<nsISimpleEnumerator> chainElt;
   642     rv = certList->GetEnumerator(getter_AddRefs(chainElt));
   643     NS_ENSURE_SUCCESS(rv, rv);
   645     // Each chain may have multiple certificates.
   646     bool hasMoreCerts = false;
   647     rv = chainElt->HasMoreElements(&hasMoreCerts);
   648     while (hasMoreCerts) {
   649       nsCOMPtr<nsISupports> supports;
   650       rv = chainElt->GetNext(getter_AddRefs(supports));
   651       NS_ENSURE_SUCCESS(rv, rv);
   653       nsCOMPtr<nsIX509Cert> cert = do_QueryInterface(supports, &rv);
   654       NS_ENSURE_SUCCESS(rv, rv);
   656       uint8_t* data = nullptr;
   657       uint32_t len = 0;
   658       rv = cert->GetRawDER(&len, &data);
   659       NS_ENSURE_SUCCESS(rv, rv);
   661       // Add this certificate to the protobuf to send remotely.
   662       certChain->add_element()->set_certificate(data, len);
   663       nsMemory::Free(data);
   665       rv = chainElt->HasMoreElements(&hasMoreCerts);
   666       NS_ENSURE_SUCCESS(rv, rv);
   667     }
   668     rv = chains->HasMoreElements(&hasMoreChains);
   669     NS_ENSURE_SUCCESS(rv, rv);
   670   }
   671   if (aSignatureInfo->certificate_chain_size() > 0) {
   672     aSignatureInfo->set_trusted(true);
   673   }
   674   return NS_OK;
   675 }
   677 nsresult
   678 PendingLookup::SendRemoteQuery()
   679 {
   680   nsresult rv = SendRemoteQueryInternal();
   681   if (NS_FAILED(rv)) {
   682     return OnComplete(false, NS_OK);
   683   }
   684   // SendRemoteQueryInternal has fired off the query and we call OnComplete in
   685   // the nsIStreamListener.onStopRequest.
   686   return rv;
   687 }
   689 nsresult
   690 PendingLookup::SendRemoteQueryInternal()
   691 {
   692   LOG(("Sending remote query for application reputation [this = %p]", this));
   693   // We did not find a local result, so fire off the query to the application
   694   // reputation service.
   695   safe_browsing::ClientDownloadRequest req;
   696   nsCOMPtr<nsIURI> uri;
   697   nsresult rv;
   698   rv = mQuery->GetSourceURI(getter_AddRefs(uri));
   699   NS_ENSURE_SUCCESS(rv, rv);
   700   nsCString spec;
   701   rv = uri->GetSpec(spec);
   702   NS_ENSURE_SUCCESS(rv, rv);
   703   req.set_url(spec.get());
   705   uint32_t fileSize;
   706   rv = mQuery->GetFileSize(&fileSize);
   707   NS_ENSURE_SUCCESS(rv, rv);
   708   req.set_length(fileSize);
   709   // We have no way of knowing whether or not a user initiated the download.
   710   req.set_user_initiated(false);
   712   nsCString locale;
   713   NS_ENSURE_SUCCESS(Preferences::GetCString(PREF_GENERAL_LOCALE, &locale),
   714                     NS_ERROR_NOT_AVAILABLE);
   715   req.set_locale(locale.get());
   716   nsCString sha256Hash;
   717   rv = mQuery->GetSha256Hash(sha256Hash);
   718   NS_ENSURE_SUCCESS(rv, rv);
   719   req.mutable_digests()->set_sha256(sha256Hash.Data());
   720   nsString fileName;
   721   rv = mQuery->GetSuggestedFileName(fileName);
   722   NS_ENSURE_SUCCESS(rv, rv);
   723   req.set_file_basename(NS_ConvertUTF16toUTF8(fileName).get());
   724   req.mutable_signature()->CopyFrom(mSignatureInfo);
   726   if (req.signature().trusted()) {
   727     LOG(("Got signed binary for remote application reputation check "
   728          "[this = %p]", this));
   729   } else {
   730     LOG(("Got unsigned binary for remote application reputation check "
   731          "[this = %p]", this));
   732   }
   734   // Serialize the protocol buffer to a string. This can only fail if we are
   735   // out of memory, or if the protocol buffer req is missing required fields
   736   // (only the URL for now).
   737   std::string serialized;
   738   if (!req.SerializeToString(&serialized)) {
   739     return NS_ERROR_UNEXPECTED;
   740   }
   742   // Set the input stream to the serialized protocol buffer
   743   nsCOMPtr<nsIStringInputStream> sstream =
   744     do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv);
   745   NS_ENSURE_SUCCESS(rv, rv);
   747   rv = sstream->SetData(serialized.c_str(), serialized.length());
   748   NS_ENSURE_SUCCESS(rv, rv);
   750   // Set up the channel to transmit the request to the service.
   751   nsCOMPtr<nsIChannel> channel;
   752   nsCString serviceUrl;
   753   NS_ENSURE_SUCCESS(Preferences::GetCString(PREF_SB_APP_REP_URL, &serviceUrl),
   754                     NS_ERROR_NOT_AVAILABLE);
   755   nsCOMPtr<nsIIOService> ios = do_GetService(NS_IOSERVICE_CONTRACTID, &rv);
   756   rv = ios->NewChannel(serviceUrl, nullptr, nullptr, getter_AddRefs(channel));
   757   NS_ENSURE_SUCCESS(rv, rv);
   759   nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel, &rv));
   760   NS_ENSURE_SUCCESS(rv, rv);
   762   // Upload the protobuf to the application reputation service.
   763   nsCOMPtr<nsIUploadChannel2> uploadChannel = do_QueryInterface(channel, &rv);
   764   NS_ENSURE_SUCCESS(rv, rv);
   766   rv = uploadChannel->ExplicitSetUploadStream(sstream,
   767     NS_LITERAL_CSTRING("application/octet-stream"), serialized.size(),
   768     NS_LITERAL_CSTRING("POST"), false);
   769   NS_ENSURE_SUCCESS(rv, rv);
   771   // Set the Safebrowsing cookie jar, so that the regular Google cookie is not
   772   // sent with this request. See bug 897516.
   773   nsCOMPtr<nsIInterfaceRequestor> loadContext =
   774     new mozilla::LoadContext(NECKO_SAFEBROWSING_APP_ID);
   775   rv = channel->SetNotificationCallbacks(loadContext);
   776   NS_ENSURE_SUCCESS(rv, rv);
   778   rv = channel->AsyncOpen(this, nullptr);
   779   NS_ENSURE_SUCCESS(rv, rv);
   781   return NS_OK;
   782 }
   784 ////////////////////////////////////////////////////////////////////////////////
   785 //// nsIStreamListener
   786 static NS_METHOD
   787 AppendSegmentToString(nsIInputStream* inputStream,
   788                       void *closure,
   789                       const char *rawSegment,
   790                       uint32_t toOffset,
   791                       uint32_t count,
   792                       uint32_t *writeCount) {
   793   nsAutoCString* decodedData = static_cast<nsAutoCString*>(closure);
   794   decodedData->Append(rawSegment, count);
   795   *writeCount = count;
   796   return NS_OK;
   797 }
   799 NS_IMETHODIMP
   800 PendingLookup::OnDataAvailable(nsIRequest *aRequest,
   801                                nsISupports *aContext,
   802                                nsIInputStream *aStream,
   803                                uint64_t offset,
   804                                uint32_t count) {
   805   uint32_t read;
   806   return aStream->ReadSegments(AppendSegmentToString, &mResponse, count, &read);
   807 }
   809 NS_IMETHODIMP
   810 PendingLookup::OnStartRequest(nsIRequest *aRequest,
   811                               nsISupports *aContext) {
   812   return NS_OK;
   813 }
   815 NS_IMETHODIMP
   816 PendingLookup::OnStopRequest(nsIRequest *aRequest,
   817                              nsISupports *aContext,
   818                              nsresult aResult) {
   819   NS_ENSURE_STATE(mCallback);
   821   bool shouldBlock = false;
   822   nsresult rv = OnStopRequestInternal(aRequest, aContext, aResult,
   823                                       &shouldBlock);
   824   OnComplete(shouldBlock, rv);
   825   return rv;
   826 }
   828 nsresult
   829 PendingLookup::OnStopRequestInternal(nsIRequest *aRequest,
   830                                      nsISupports *aContext,
   831                                      nsresult aResult,
   832                                      bool* aShouldBlock) {
   833   if (NS_FAILED(aResult)) {
   834     Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_SERVER,
   835       SERVER_RESPONSE_FAILED);
   836     return aResult;
   837   }
   839   *aShouldBlock = false;
   840   nsresult rv;
   841   nsCOMPtr<nsIHttpChannel> channel = do_QueryInterface(aRequest, &rv);
   842   if (NS_FAILED(rv)) {
   843     Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_SERVER,
   844       SERVER_RESPONSE_FAILED);
   845     return rv;
   846   }
   848   uint32_t status = 0;
   849   rv = channel->GetResponseStatus(&status);
   850   if (NS_FAILED(rv)) {
   851     Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_SERVER,
   852       SERVER_RESPONSE_FAILED);
   853     return rv;
   854   }
   856   if (status != 200) {
   857     Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_SERVER,
   858       SERVER_RESPONSE_FAILED);
   859     return NS_ERROR_NOT_AVAILABLE;
   860   }
   862   std::string buf(mResponse.Data(), mResponse.Length());
   863   safe_browsing::ClientDownloadResponse response;
   864   if (!response.ParseFromString(buf)) {
   865     NS_WARNING("Could not parse protocol buffer");
   866     Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_SERVER,
   867                                    SERVER_RESPONSE_INVALID);
   868     return NS_ERROR_CANNOT_CONVERT_DATA;
   869   }
   871   // There are several more verdicts, but we only respect one for now and treat
   872   // everything else as SAFE.
   873   Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_SERVER,
   874     SERVER_RESPONSE_VALID);
   875   if (response.verdict() == safe_browsing::ClientDownloadResponse::DANGEROUS) {
   876     *aShouldBlock = true;
   877   }
   879   return NS_OK;
   880 }
   882 NS_IMPL_ISUPPORTS(ApplicationReputationService,
   883                   nsIApplicationReputationService)
   885 ApplicationReputationService*
   886   ApplicationReputationService::gApplicationReputationService = nullptr;
   888 ApplicationReputationService*
   889 ApplicationReputationService::GetSingleton()
   890 {
   891   if (gApplicationReputationService) {
   892     NS_ADDREF(gApplicationReputationService);
   893     return gApplicationReputationService;
   894   }
   896   // We're not initialized yet.
   897   gApplicationReputationService = new ApplicationReputationService();
   898   if (gApplicationReputationService) {
   899     NS_ADDREF(gApplicationReputationService);
   900   }
   902   return gApplicationReputationService;
   903 }
   905 ApplicationReputationService::ApplicationReputationService()
   906 {
   907 #if defined(PR_LOGGING)
   908   if (!prlog) {
   909     prlog = PR_NewLogModule("ApplicationReputation");
   910   }
   911 #endif
   912   LOG(("Application reputation service started up"));
   913 }
   915 ApplicationReputationService::~ApplicationReputationService() {
   916   LOG(("Application reputation service shutting down"));
   917 }
   919 NS_IMETHODIMP
   920 ApplicationReputationService::QueryReputation(
   921     nsIApplicationReputationQuery* aQuery,
   922     nsIApplicationReputationCallback* aCallback) {
   923   NS_ENSURE_ARG_POINTER(aQuery);
   924   NS_ENSURE_ARG_POINTER(aCallback);
   926   Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_COUNT, true);
   927   nsresult rv = QueryReputationInternal(aQuery, aCallback);
   928   if (NS_FAILED(rv)) {
   929     Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_SHOULD_BLOCK,
   930       false);
   931     aCallback->OnComplete(false, rv);
   932   }
   933   return NS_OK;
   934 }
   936 nsresult ApplicationReputationService::QueryReputationInternal(
   937   nsIApplicationReputationQuery* aQuery,
   938   nsIApplicationReputationCallback* aCallback) {
   939   nsresult rv;
   940   // If malware checks aren't enabled, don't query application reputation.
   941   if (!Preferences::GetBool(PREF_SB_MALWARE_ENABLED, false)) {
   942     return NS_ERROR_NOT_AVAILABLE;
   943   }
   945   // If there is no service URL for querying application reputation, abort.
   946   nsCString serviceUrl;
   947   NS_ENSURE_SUCCESS(Preferences::GetCString(PREF_SB_APP_REP_URL, &serviceUrl),
   948                     NS_ERROR_NOT_AVAILABLE);
   949   if (serviceUrl.EqualsLiteral("")) {
   950     return NS_ERROR_NOT_AVAILABLE;
   951   }
   953   nsCOMPtr<nsIURI> uri;
   954   rv = aQuery->GetSourceURI(getter_AddRefs(uri));
   955   NS_ENSURE_SUCCESS(rv, rv);
   956   // Bail if the URI hasn't been set.
   957   NS_ENSURE_STATE(uri);
   959   // Create a new pending lookup and start the call chain.
   960   nsRefPtr<PendingLookup> lookup(new PendingLookup(aQuery, aCallback));
   961   NS_ENSURE_STATE(lookup);
   963   return lookup->StartLookup();
   964 }

mercurial