1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/components/downloads/ApplicationReputation.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,964 @@ 1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim: set ts=2 et sw=2 tw=80: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 +// See 1.10 +// https://wiki.mozilla.org/Security/Features/Application_Reputation_Design_Doc 1.11 +// for a description of Chrome's implementation of this feature. 1.12 +#include "ApplicationReputation.h" 1.13 +#include "csd.pb.h" 1.14 + 1.15 +#include "nsIArray.h" 1.16 +#include "nsIApplicationReputation.h" 1.17 +#include "nsIChannel.h" 1.18 +#include "nsIHttpChannel.h" 1.19 +#include "nsIIOService.h" 1.20 +#include "nsIPrefService.h" 1.21 +#include "nsIScriptSecurityManager.h" 1.22 +#include "nsIStreamListener.h" 1.23 +#include "nsIStringStream.h" 1.24 +#include "nsIUploadChannel2.h" 1.25 +#include "nsIURI.h" 1.26 +#include "nsIUrlClassifierDBService.h" 1.27 +#include "nsIX509Cert.h" 1.28 +#include "nsIX509CertDB.h" 1.29 +#include "nsIX509CertList.h" 1.30 + 1.31 +#include "mozilla/Preferences.h" 1.32 +#include "mozilla/Services.h" 1.33 +#include "mozilla/Telemetry.h" 1.34 +#include "mozilla/TimeStamp.h" 1.35 +#include "mozilla/LoadContext.h" 1.36 + 1.37 +#include "nsAutoPtr.h" 1.38 +#include "nsCOMPtr.h" 1.39 +#include "nsDebug.h" 1.40 +#include "nsError.h" 1.41 +#include "nsNetCID.h" 1.42 +#include "nsReadableUtils.h" 1.43 +#include "nsServiceManagerUtils.h" 1.44 +#include "nsString.h" 1.45 +#include "nsTArray.h" 1.46 +#include "nsThreadUtils.h" 1.47 +#include "nsXPCOMStrings.h" 1.48 + 1.49 +using mozilla::Preferences; 1.50 +using mozilla::TimeStamp; 1.51 +using mozilla::Telemetry::Accumulate; 1.52 +using safe_browsing::ClientDownloadRequest; 1.53 +using safe_browsing::ClientDownloadRequest_SignatureInfo; 1.54 +using safe_browsing::ClientDownloadRequest_CertificateChain; 1.55 + 1.56 +// Preferences that we need to initialize the query. 1.57 +#define PREF_SB_APP_REP_URL "browser.safebrowsing.appRepURL" 1.58 +#define PREF_SB_MALWARE_ENABLED "browser.safebrowsing.malware.enabled" 1.59 +#define PREF_GENERAL_LOCALE "general.useragent.locale" 1.60 +#define PREF_DOWNLOAD_BLOCK_TABLE "urlclassifier.downloadBlockTable" 1.61 +#define PREF_DOWNLOAD_ALLOW_TABLE "urlclassifier.downloadAllowTable" 1.62 + 1.63 +// NSPR_LOG_MODULES=ApplicationReputation:5 1.64 +#if defined(PR_LOGGING) 1.65 +PRLogModuleInfo *ApplicationReputationService::prlog = nullptr; 1.66 +#define LOG(args) PR_LOG(ApplicationReputationService::prlog, PR_LOG_DEBUG, args) 1.67 +#define LOG_ENABLED() PR_LOG_TEST(ApplicationReputationService::prlog, 4) 1.68 +#else 1.69 +#define LOG(args) 1.70 +#define LOG_ENABLED() (false) 1.71 +#endif 1.72 + 1.73 +class PendingDBLookup; 1.74 + 1.75 +// A single use class private to ApplicationReputationService encapsulating an 1.76 +// nsIApplicationReputationQuery and an nsIApplicationReputationCallback. Once 1.77 +// created by ApplicationReputationService, it is guaranteed to call mCallback. 1.78 +// This class is private to ApplicationReputationService. 1.79 +class PendingLookup MOZ_FINAL : public nsIStreamListener 1.80 +{ 1.81 +public: 1.82 + NS_DECL_ISUPPORTS 1.83 + NS_DECL_NSIREQUESTOBSERVER 1.84 + NS_DECL_NSISTREAMLISTENER 1.85 + 1.86 + // Constructor and destructor. 1.87 + PendingLookup(nsIApplicationReputationQuery* aQuery, 1.88 + nsIApplicationReputationCallback* aCallback); 1.89 + ~PendingLookup(); 1.90 + 1.91 + // Start the lookup. The lookup may have 2 parts: local and remote. In the 1.92 + // local lookup, PendingDBLookups are created to query the local allow and 1.93 + // blocklists for various URIs associated with this downloaded file. In the 1.94 + // event that no results are found, a remote lookup is sent to the Application 1.95 + // Reputation server. 1.96 + nsresult StartLookup(); 1.97 + 1.98 +private: 1.99 + friend class PendingDBLookup; 1.100 + 1.101 + // Telemetry states. 1.102 + // Status of the remote response (valid or not). 1.103 + enum SERVER_RESPONSE_TYPES { 1.104 + SERVER_RESPONSE_VALID = 0, 1.105 + SERVER_RESPONSE_FAILED = 1, 1.106 + SERVER_RESPONSE_INVALID = 2, 1.107 + }; 1.108 + 1.109 + // The query containing metadata about the downloaded file. 1.110 + nsCOMPtr<nsIApplicationReputationQuery> mQuery; 1.111 + 1.112 + // The callback with which to report the verdict. 1.113 + nsCOMPtr<nsIApplicationReputationCallback> mCallback; 1.114 + 1.115 + // An array of strings created from certificate information used to whitelist 1.116 + // the downloaded file. 1.117 + nsTArray<nsCString> mAllowlistSpecs; 1.118 + // The source URI of the download, the referrer and possibly any redirects. 1.119 + nsTArray<nsCString> mAnylistSpecs; 1.120 + 1.121 + // When we started this query 1.122 + TimeStamp mStartTime; 1.123 + 1.124 + // The protocol buffer used to store signature information extracted using 1.125 + // the Windows Authenticode API, if the binary is signed. 1.126 + ClientDownloadRequest_SignatureInfo mSignatureInfo; 1.127 + 1.128 + // The response from the application reputation query. This is read in chunks 1.129 + // as part of our nsIStreamListener implementation and may contain embedded 1.130 + // NULLs. 1.131 + nsCString mResponse; 1.132 + 1.133 + // Returns true if the file is likely to be binary on Windows. 1.134 + bool IsBinaryFile(); 1.135 + 1.136 + // Clean up and call the callback. PendingLookup must not be used after this 1.137 + // function is called. 1.138 + nsresult OnComplete(bool shouldBlock, nsresult rv); 1.139 + 1.140 + // Wrapper function for nsIStreamListener.onStopRequest to make it easy to 1.141 + // guarantee calling the callback 1.142 + nsresult OnStopRequestInternal(nsIRequest *aRequest, 1.143 + nsISupports *aContext, 1.144 + nsresult aResult, 1.145 + bool* aShouldBlock); 1.146 + 1.147 + // Escape '/' and '%' in certificate attribute values. 1.148 + nsCString EscapeCertificateAttribute(const nsACString& aAttribute); 1.149 + 1.150 + // Escape ':' in fingerprint values. 1.151 + nsCString EscapeFingerprint(const nsACString& aAttribute); 1.152 + 1.153 + // Generate whitelist strings for the given certificate pair from the same 1.154 + // certificate chain. 1.155 + nsresult GenerateWhitelistStringsForPair( 1.156 + nsIX509Cert* certificate, nsIX509Cert* issuer); 1.157 + 1.158 + // Generate whitelist strings for the given certificate chain, which starts 1.159 + // with the signer and may go all the way to the root cert. 1.160 + nsresult GenerateWhitelistStringsForChain( 1.161 + const ClientDownloadRequest_CertificateChain& aChain); 1.162 + 1.163 + // For signed binaries, generate strings of the form: 1.164 + // http://sb-ssl.google.com/safebrowsing/csd/certificate/ 1.165 + // <issuer_cert_sha1_fingerprint>[/CN=<cn>][/O=<org>][/OU=<unit>] 1.166 + // for each (cert, issuer) pair in each chain of certificates that is 1.167 + // associated with the binary. 1.168 + nsresult GenerateWhitelistStrings( 1.169 + const ClientDownloadRequest_SignatureInfo& aSignatureInfo); 1.170 + 1.171 + // Parse the XPCOM certificate lists and stick them into the protocol buffer 1.172 + // version. 1.173 + nsresult ParseCertificates(nsIArray* aSigArray, 1.174 + ClientDownloadRequest_SignatureInfo* aSigInfo); 1.175 + 1.176 + // Helper function to ensure that we call PendingLookup::LookupNext or 1.177 + // PendingLookup::OnComplete. 1.178 + nsresult DoLookupInternal(); 1.179 + 1.180 + // Looks up all the URIs that may be responsible for allowlisting or 1.181 + // blocklisting the downloaded file. These URIs may include whitelist strings 1.182 + // generated by certificates verifying the binary as well as the target URI 1.183 + // from which the file was downloaded. 1.184 + nsresult LookupNext(); 1.185 + 1.186 + // Sends a query to the remote application reputation service. Returns NS_OK 1.187 + // on success. 1.188 + nsresult SendRemoteQuery(); 1.189 + 1.190 + // Helper function to ensure that we always call the callback. 1.191 + nsresult SendRemoteQueryInternal(); 1.192 +}; 1.193 + 1.194 +// A single-use class for looking up a single URI in the safebrowsing DB. This 1.195 +// class is private to PendingLookup. 1.196 +class PendingDBLookup MOZ_FINAL : public nsIUrlClassifierCallback 1.197 +{ 1.198 +public: 1.199 + NS_DECL_ISUPPORTS 1.200 + NS_DECL_NSIURLCLASSIFIERCALLBACK 1.201 + 1.202 + // Constructor and destructor 1.203 + PendingDBLookup(PendingLookup* aPendingLookup); 1.204 + ~PendingDBLookup(); 1.205 + 1.206 + // Look up the given URI in the safebrowsing DBs, optionally on both the allow 1.207 + // list and the blocklist. If there is a match, call 1.208 + // PendingLookup::OnComplete. Otherwise, call PendingLookup::LookupNext. 1.209 + nsresult LookupSpec(const nsACString& aSpec, bool aAllowlistOnly); 1.210 +private: 1.211 + // The download appeared on the allowlist, blocklist, or no list (and thus 1.212 + // could trigger a remote query. 1.213 + enum LIST_TYPES { 1.214 + ALLOW_LIST = 0, 1.215 + BLOCK_LIST = 1, 1.216 + NO_LIST = 2, 1.217 + }; 1.218 + 1.219 + nsCString mSpec; 1.220 + bool mAllowlistOnly; 1.221 + nsRefPtr<PendingLookup> mPendingLookup; 1.222 + nsresult LookupSpecInternal(const nsACString& aSpec); 1.223 +}; 1.224 + 1.225 +NS_IMPL_ISUPPORTS(PendingDBLookup, 1.226 + nsIUrlClassifierCallback) 1.227 + 1.228 +PendingDBLookup::PendingDBLookup(PendingLookup* aPendingLookup) : 1.229 + mAllowlistOnly(false), 1.230 + mPendingLookup(aPendingLookup) 1.231 +{ 1.232 + LOG(("Created pending DB lookup [this = %p]", this)); 1.233 +} 1.234 + 1.235 +PendingDBLookup::~PendingDBLookup() 1.236 +{ 1.237 + LOG(("Destroying pending DB lookup [this = %p]", this)); 1.238 + mPendingLookup = nullptr; 1.239 +} 1.240 + 1.241 +nsresult 1.242 +PendingDBLookup::LookupSpec(const nsACString& aSpec, 1.243 + bool aAllowlistOnly) 1.244 +{ 1.245 + LOG(("Checking principal %s", aSpec.Data())); 1.246 + mSpec = aSpec; 1.247 + mAllowlistOnly = aAllowlistOnly; 1.248 + nsresult rv = LookupSpecInternal(aSpec); 1.249 + if (NS_FAILED(rv)) { 1.250 + LOG(("Error in LookupSpecInternal")); 1.251 + return mPendingLookup->OnComplete(false, NS_OK); 1.252 + } 1.253 + // LookupSpecInternal has called nsIUrlClassifierCallback.lookup, which is 1.254 + // guaranteed to call HandleEvent. 1.255 + return rv; 1.256 +} 1.257 + 1.258 +nsresult 1.259 +PendingDBLookup::LookupSpecInternal(const nsACString& aSpec) 1.260 +{ 1.261 + nsresult rv; 1.262 + 1.263 + nsCOMPtr<nsIURI> uri; 1.264 + nsCOMPtr<nsIIOService> ios = do_GetService(NS_IOSERVICE_CONTRACTID, &rv); 1.265 + rv = ios->NewURI(aSpec, nullptr, nullptr, getter_AddRefs(uri)); 1.266 + NS_ENSURE_SUCCESS(rv, rv); 1.267 + 1.268 + nsCOMPtr<nsIPrincipal> principal; 1.269 + nsCOMPtr<nsIScriptSecurityManager> secMan = 1.270 + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv); 1.271 + NS_ENSURE_SUCCESS(rv, rv); 1.272 + 1.273 + rv = secMan->GetNoAppCodebasePrincipal(uri, getter_AddRefs(principal)); 1.274 + NS_ENSURE_SUCCESS(rv, rv); 1.275 + 1.276 + // Check local lists to see if the URI has already been whitelisted or 1.277 + // blacklisted. 1.278 + LOG(("Checking DB service for principal %s [this = %p]", mSpec.get(), this)); 1.279 + nsCOMPtr<nsIUrlClassifierDBService> dbService = 1.280 + do_GetService(NS_URLCLASSIFIERDBSERVICE_CONTRACTID, &rv); 1.281 + nsAutoCString tables; 1.282 + nsAutoCString allowlist; 1.283 + Preferences::GetCString(PREF_DOWNLOAD_ALLOW_TABLE, &allowlist); 1.284 + if (!allowlist.IsEmpty()) { 1.285 + tables.Append(allowlist); 1.286 + } 1.287 + nsAutoCString blocklist; 1.288 + Preferences::GetCString(PREF_DOWNLOAD_BLOCK_TABLE, &blocklist); 1.289 + if (!mAllowlistOnly && !blocklist.IsEmpty()) { 1.290 + tables.Append(","); 1.291 + tables.Append(blocklist); 1.292 + } 1.293 + return dbService->Lookup(principal, tables, this); 1.294 +} 1.295 + 1.296 +NS_IMETHODIMP 1.297 +PendingDBLookup::HandleEvent(const nsACString& tables) 1.298 +{ 1.299 + // HandleEvent is guaranteed to call either: 1.300 + // 1) PendingLookup::OnComplete if the URL can be classified locally, or 1.301 + // 2) PendingLookup::LookupNext if the URL can be cannot classified locally. 1.302 + // Blocklisting trumps allowlisting. 1.303 + nsAutoCString blockList; 1.304 + Preferences::GetCString(PREF_DOWNLOAD_BLOCK_TABLE, &blockList); 1.305 + if (!mAllowlistOnly && FindInReadable(blockList, tables)) { 1.306 + Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_LOCAL, BLOCK_LIST); 1.307 + LOG(("Found principal %s on blocklist [this = %p]", mSpec.get(), this)); 1.308 + return mPendingLookup->OnComplete(true, NS_OK); 1.309 + } 1.310 + 1.311 + nsAutoCString allowList; 1.312 + Preferences::GetCString(PREF_DOWNLOAD_ALLOW_TABLE, &allowList); 1.313 + if (FindInReadable(allowList, tables)) { 1.314 + Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_LOCAL, ALLOW_LIST); 1.315 + LOG(("Found principal %s on allowlist [this = %p]", mSpec.get(), this)); 1.316 + return mPendingLookup->OnComplete(false, NS_OK); 1.317 + } 1.318 + 1.319 + LOG(("Didn't find principal %s on any list [this = %p]", mSpec.get(), this)); 1.320 + Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_LOCAL, NO_LIST); 1.321 + return mPendingLookup->LookupNext(); 1.322 +} 1.323 + 1.324 +NS_IMPL_ISUPPORTS(PendingLookup, 1.325 + nsIStreamListener, 1.326 + nsIRequestObserver) 1.327 + 1.328 +PendingLookup::PendingLookup(nsIApplicationReputationQuery* aQuery, 1.329 + nsIApplicationReputationCallback* aCallback) : 1.330 + mQuery(aQuery), 1.331 + mCallback(aCallback) 1.332 +{ 1.333 + LOG(("Created pending lookup [this = %p]", this)); 1.334 +} 1.335 + 1.336 +PendingLookup::~PendingLookup() 1.337 +{ 1.338 + LOG(("Destroying pending lookup [this = %p]", this)); 1.339 +} 1.340 + 1.341 +bool 1.342 +PendingLookup::IsBinaryFile() 1.343 +{ 1.344 + nsString fileName; 1.345 + nsresult rv = mQuery->GetSuggestedFileName(fileName); 1.346 + if (NS_FAILED(rv)) { 1.347 + return false; 1.348 + } 1.349 + return 1.350 + // Executable extensions for MS Windows, from 1.351 + // https://code.google.com/p/chromium/codesearch#chromium/src/chrome/common/safe_browsing/download_protection_util.cc&l=14 1.352 + StringEndsWith(fileName, NS_LITERAL_STRING(".apk")) || 1.353 + StringEndsWith(fileName, NS_LITERAL_STRING(".bas")) || 1.354 + StringEndsWith(fileName, NS_LITERAL_STRING(".bat")) || 1.355 + StringEndsWith(fileName, NS_LITERAL_STRING(".cab")) || 1.356 + StringEndsWith(fileName, NS_LITERAL_STRING(".cmd")) || 1.357 + StringEndsWith(fileName, NS_LITERAL_STRING(".com")) || 1.358 + StringEndsWith(fileName, NS_LITERAL_STRING(".exe")) || 1.359 + StringEndsWith(fileName, NS_LITERAL_STRING(".hta")) || 1.360 + StringEndsWith(fileName, NS_LITERAL_STRING(".msi")) || 1.361 + StringEndsWith(fileName, NS_LITERAL_STRING(".pif")) || 1.362 + StringEndsWith(fileName, NS_LITERAL_STRING(".reg")) || 1.363 + StringEndsWith(fileName, NS_LITERAL_STRING(".scr")) || 1.364 + StringEndsWith(fileName, NS_LITERAL_STRING(".vb")) || 1.365 + StringEndsWith(fileName, NS_LITERAL_STRING(".vbs")) || 1.366 + StringEndsWith(fileName, NS_LITERAL_STRING(".zip")); 1.367 +} 1.368 + 1.369 +nsresult 1.370 +PendingLookup::LookupNext() 1.371 +{ 1.372 + // We must call LookupNext or SendRemoteQuery upon return. 1.373 + // Look up all of the URLs that could whitelist this download. 1.374 + // Blacklist first. 1.375 + int index = mAnylistSpecs.Length() - 1; 1.376 + nsCString spec; 1.377 + bool allowlistOnly = false; 1.378 + if (index >= 0) { 1.379 + // Check the source URI and referrer. 1.380 + spec = mAnylistSpecs[index]; 1.381 + mAnylistSpecs.RemoveElementAt(index); 1.382 + } else { 1.383 + // Check the allowlists next. 1.384 + index = mAllowlistSpecs.Length() - 1; 1.385 + if (index >= 0) { 1.386 + allowlistOnly = true; 1.387 + spec = mAllowlistSpecs[index]; 1.388 + mAllowlistSpecs.RemoveElementAt(index); 1.389 + } 1.390 + } 1.391 + if (index >= 0) { 1.392 + nsRefPtr<PendingDBLookup> lookup(new PendingDBLookup(this)); 1.393 + return lookup->LookupSpec(spec, allowlistOnly); 1.394 + } 1.395 +#ifdef XP_WIN 1.396 + // There are no more URIs to check against local list. If the file is not 1.397 + // eligible for remote lookup, bail. 1.398 + if (!IsBinaryFile()) { 1.399 + LOG(("Not eligible for remote lookups [this=%x]", this)); 1.400 + return OnComplete(false, NS_OK); 1.401 + } 1.402 + // Send the remote query if we are on Windows. 1.403 + nsresult rv = SendRemoteQuery(); 1.404 + if (NS_FAILED(rv)) { 1.405 + return OnComplete(false, rv); 1.406 + } 1.407 + return NS_OK; 1.408 +#else 1.409 + return OnComplete(false, NS_OK); 1.410 +#endif 1.411 +} 1.412 + 1.413 +nsCString 1.414 +PendingLookup::EscapeCertificateAttribute(const nsACString& aAttribute) 1.415 +{ 1.416 + // Escape '/' because it's a field separator, and '%' because Chrome does 1.417 + nsCString escaped; 1.418 + escaped.SetCapacity(aAttribute.Length()); 1.419 + for (unsigned int i = 0; i < aAttribute.Length(); ++i) { 1.420 + if (aAttribute.Data()[i] == '%') { 1.421 + escaped.Append("%25"); 1.422 + } else if (aAttribute.Data()[i] == '/') { 1.423 + escaped.Append("%2F"); 1.424 + } else if (aAttribute.Data()[i] == ' ') { 1.425 + escaped.Append("%20"); 1.426 + } else { 1.427 + escaped.Append(aAttribute.Data()[i]); 1.428 + } 1.429 + } 1.430 + return escaped; 1.431 +} 1.432 + 1.433 +nsCString 1.434 +PendingLookup::EscapeFingerprint(const nsACString& aFingerprint) 1.435 +{ 1.436 + // Google's fingerprint doesn't have colons 1.437 + nsCString escaped; 1.438 + escaped.SetCapacity(aFingerprint.Length()); 1.439 + for (unsigned int i = 0; i < aFingerprint.Length(); ++i) { 1.440 + if (aFingerprint.Data()[i] != ':') { 1.441 + escaped.Append(aFingerprint.Data()[i]); 1.442 + } 1.443 + } 1.444 + return escaped; 1.445 +} 1.446 + 1.447 +nsresult 1.448 +PendingLookup::GenerateWhitelistStringsForPair( 1.449 + nsIX509Cert* certificate, 1.450 + nsIX509Cert* issuer) 1.451 +{ 1.452 + // The whitelist paths have format: 1.453 + // http://sb-ssl.google.com/safebrowsing/csd/certificate/<issuer_cert_fingerprint>[/CN=<cn>][/O=<org>][/OU=<unit>] 1.454 + // Any of CN, O, or OU may be omitted from the whitelist entry. Unfortunately 1.455 + // this is not publicly documented, but the Chrome implementation can be found 1.456 + // here: 1.457 + // https://code.google.com/p/chromium/codesearch#search/&q=GetCertificateWhitelistStrings 1.458 + nsCString whitelistString( 1.459 + "http://sb-ssl.google.com/safebrowsing/csd/certificate/"); 1.460 + 1.461 + nsString fingerprint; 1.462 + nsresult rv = issuer->GetSha1Fingerprint(fingerprint); 1.463 + NS_ENSURE_SUCCESS(rv, rv); 1.464 + whitelistString.Append( 1.465 + EscapeFingerprint(NS_ConvertUTF16toUTF8(fingerprint))); 1.466 + 1.467 + nsString commonName; 1.468 + rv = certificate->GetCommonName(commonName); 1.469 + NS_ENSURE_SUCCESS(rv, rv); 1.470 + if (!commonName.IsEmpty()) { 1.471 + whitelistString.Append("/CN="); 1.472 + whitelistString.Append( 1.473 + EscapeCertificateAttribute(NS_ConvertUTF16toUTF8(commonName))); 1.474 + } 1.475 + 1.476 + nsString organization; 1.477 + rv = certificate->GetOrganization(organization); 1.478 + NS_ENSURE_SUCCESS(rv, rv); 1.479 + if (!organization.IsEmpty()) { 1.480 + whitelistString.Append("/O="); 1.481 + whitelistString.Append( 1.482 + EscapeCertificateAttribute(NS_ConvertUTF16toUTF8(organization))); 1.483 + } 1.484 + 1.485 + nsString organizationalUnit; 1.486 + rv = certificate->GetOrganizationalUnit(organizationalUnit); 1.487 + NS_ENSURE_SUCCESS(rv, rv); 1.488 + if (!organizationalUnit.IsEmpty()) { 1.489 + whitelistString.Append("/OU="); 1.490 + whitelistString.Append( 1.491 + EscapeCertificateAttribute(NS_ConvertUTF16toUTF8(organizationalUnit))); 1.492 + } 1.493 + LOG(("Whitelisting %s", whitelistString.get())); 1.494 + 1.495 + mAllowlistSpecs.AppendElement(whitelistString); 1.496 + return NS_OK; 1.497 +} 1.498 + 1.499 +nsresult 1.500 +PendingLookup::GenerateWhitelistStringsForChain( 1.501 + const safe_browsing::ClientDownloadRequest_CertificateChain& aChain) 1.502 +{ 1.503 + // We need a signing certificate and an issuer to construct a whitelist 1.504 + // entry. 1.505 + if (aChain.element_size() < 2) { 1.506 + return NS_OK; 1.507 + } 1.508 + 1.509 + // Get the signer. 1.510 + nsresult rv; 1.511 + nsCOMPtr<nsIX509CertDB> certDB = do_GetService(NS_X509CERTDB_CONTRACTID, &rv); 1.512 + NS_ENSURE_SUCCESS(rv, rv); 1.513 + 1.514 + nsCOMPtr<nsIX509Cert> signer; 1.515 + rv = certDB->ConstructX509( 1.516 + const_cast<char *>(aChain.element(0).certificate().data()), 1.517 + aChain.element(0).certificate().size(), getter_AddRefs(signer)); 1.518 + NS_ENSURE_SUCCESS(rv, rv); 1.519 + 1.520 + for (int i = 1; i < aChain.element_size(); ++i) { 1.521 + // Get the issuer. 1.522 + nsCOMPtr<nsIX509Cert> issuer; 1.523 + rv = certDB->ConstructX509( 1.524 + const_cast<char *>(aChain.element(i).certificate().data()), 1.525 + aChain.element(i).certificate().size(), getter_AddRefs(issuer)); 1.526 + NS_ENSURE_SUCCESS(rv, rv); 1.527 + 1.528 + nsresult rv = GenerateWhitelistStringsForPair(signer, issuer); 1.529 + NS_ENSURE_SUCCESS(rv, rv); 1.530 + } 1.531 + return NS_OK; 1.532 +} 1.533 + 1.534 +nsresult 1.535 +PendingLookup::GenerateWhitelistStrings( 1.536 + const safe_browsing::ClientDownloadRequest_SignatureInfo& aSignatureInfo) 1.537 +{ 1.538 + for (int i = 0; i < aSignatureInfo.certificate_chain_size(); ++i) { 1.539 + nsresult rv = GenerateWhitelistStringsForChain( 1.540 + aSignatureInfo.certificate_chain(i)); 1.541 + NS_ENSURE_SUCCESS(rv, rv); 1.542 + } 1.543 + return NS_OK; 1.544 +} 1.545 + 1.546 +nsresult 1.547 +PendingLookup::StartLookup() 1.548 +{ 1.549 + mStartTime = TimeStamp::Now(); 1.550 + nsresult rv = DoLookupInternal(); 1.551 + if (NS_FAILED(rv)) { 1.552 + return OnComplete(false, NS_OK); 1.553 + }; 1.554 + return rv; 1.555 +} 1.556 + 1.557 +nsresult 1.558 +PendingLookup::DoLookupInternal() 1.559 +{ 1.560 + // We want to check the target URI against the local lists. 1.561 + nsCOMPtr<nsIURI> uri; 1.562 + nsresult rv = mQuery->GetSourceURI(getter_AddRefs(uri)); 1.563 + NS_ENSURE_SUCCESS(rv, rv); 1.564 + 1.565 + nsCString spec; 1.566 + rv = uri->GetSpec(spec); 1.567 + NS_ENSURE_SUCCESS(rv, rv); 1.568 + mAnylistSpecs.AppendElement(spec); 1.569 + 1.570 + nsCOMPtr<nsIURI> referrer = nullptr; 1.571 + rv = mQuery->GetReferrerURI(getter_AddRefs(referrer)); 1.572 + if (referrer) { 1.573 + nsCString spec; 1.574 + rv = referrer->GetSpec(spec); 1.575 + NS_ENSURE_SUCCESS(rv, rv); 1.576 + mAnylistSpecs.AppendElement(spec); 1.577 + } 1.578 + 1.579 + // Extract the signature and parse certificates so we can use it to check 1.580 + // whitelists. 1.581 + nsCOMPtr<nsIArray> sigArray; 1.582 + rv = mQuery->GetSignatureInfo(getter_AddRefs(sigArray)); 1.583 + NS_ENSURE_SUCCESS(rv, rv); 1.584 + 1.585 + if (sigArray) { 1.586 + rv = ParseCertificates(sigArray, &mSignatureInfo); 1.587 + NS_ENSURE_SUCCESS(rv, rv); 1.588 + } 1.589 + 1.590 + rv = GenerateWhitelistStrings(mSignatureInfo); 1.591 + NS_ENSURE_SUCCESS(rv, rv); 1.592 + 1.593 + // Start the call chain. 1.594 + return LookupNext(); 1.595 +} 1.596 + 1.597 +nsresult 1.598 +PendingLookup::OnComplete(bool shouldBlock, nsresult rv) 1.599 +{ 1.600 + Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_SHOULD_BLOCK, 1.601 + shouldBlock); 1.602 +#if defined(PR_LOGGING) 1.603 + double t = (TimeStamp::Now() - mStartTime).ToMilliseconds(); 1.604 +#endif 1.605 + if (shouldBlock) { 1.606 + LOG(("Application Reputation check failed, blocking bad binary in %f ms " 1.607 + "[this = %p]", t, this)); 1.608 + } else { 1.609 + LOG(("Application Reputation check passed in %f ms [this = %p]", t, this)); 1.610 + } 1.611 + nsresult res = mCallback->OnComplete(shouldBlock, rv); 1.612 + return res; 1.613 +} 1.614 + 1.615 +nsresult 1.616 +PendingLookup::ParseCertificates( 1.617 + nsIArray* aSigArray, 1.618 + ClientDownloadRequest_SignatureInfo* aSignatureInfo) 1.619 +{ 1.620 + // If we haven't been set for any reason, bail. 1.621 + NS_ENSURE_ARG_POINTER(aSigArray); 1.622 + 1.623 + // Binaries may be signed by multiple chains of certificates. If there are no 1.624 + // chains, the binary is unsigned (or we were unable to extract signature 1.625 + // information on a non-Windows platform) 1.626 + nsCOMPtr<nsISimpleEnumerator> chains; 1.627 + nsresult rv = aSigArray->Enumerate(getter_AddRefs(chains)); 1.628 + NS_ENSURE_SUCCESS(rv, rv); 1.629 + 1.630 + bool hasMoreChains = false; 1.631 + rv = chains->HasMoreElements(&hasMoreChains); 1.632 + NS_ENSURE_SUCCESS(rv, rv); 1.633 + 1.634 + while (hasMoreChains) { 1.635 + nsCOMPtr<nsISupports> supports; 1.636 + rv = chains->GetNext(getter_AddRefs(supports)); 1.637 + NS_ENSURE_SUCCESS(rv, rv); 1.638 + 1.639 + nsCOMPtr<nsIX509CertList> certList = do_QueryInterface(supports, &rv); 1.640 + NS_ENSURE_SUCCESS(rv, rv); 1.641 + 1.642 + safe_browsing::ClientDownloadRequest_CertificateChain* certChain = 1.643 + aSignatureInfo->add_certificate_chain(); 1.644 + nsCOMPtr<nsISimpleEnumerator> chainElt; 1.645 + rv = certList->GetEnumerator(getter_AddRefs(chainElt)); 1.646 + NS_ENSURE_SUCCESS(rv, rv); 1.647 + 1.648 + // Each chain may have multiple certificates. 1.649 + bool hasMoreCerts = false; 1.650 + rv = chainElt->HasMoreElements(&hasMoreCerts); 1.651 + while (hasMoreCerts) { 1.652 + nsCOMPtr<nsISupports> supports; 1.653 + rv = chainElt->GetNext(getter_AddRefs(supports)); 1.654 + NS_ENSURE_SUCCESS(rv, rv); 1.655 + 1.656 + nsCOMPtr<nsIX509Cert> cert = do_QueryInterface(supports, &rv); 1.657 + NS_ENSURE_SUCCESS(rv, rv); 1.658 + 1.659 + uint8_t* data = nullptr; 1.660 + uint32_t len = 0; 1.661 + rv = cert->GetRawDER(&len, &data); 1.662 + NS_ENSURE_SUCCESS(rv, rv); 1.663 + 1.664 + // Add this certificate to the protobuf to send remotely. 1.665 + certChain->add_element()->set_certificate(data, len); 1.666 + nsMemory::Free(data); 1.667 + 1.668 + rv = chainElt->HasMoreElements(&hasMoreCerts); 1.669 + NS_ENSURE_SUCCESS(rv, rv); 1.670 + } 1.671 + rv = chains->HasMoreElements(&hasMoreChains); 1.672 + NS_ENSURE_SUCCESS(rv, rv); 1.673 + } 1.674 + if (aSignatureInfo->certificate_chain_size() > 0) { 1.675 + aSignatureInfo->set_trusted(true); 1.676 + } 1.677 + return NS_OK; 1.678 +} 1.679 + 1.680 +nsresult 1.681 +PendingLookup::SendRemoteQuery() 1.682 +{ 1.683 + nsresult rv = SendRemoteQueryInternal(); 1.684 + if (NS_FAILED(rv)) { 1.685 + return OnComplete(false, NS_OK); 1.686 + } 1.687 + // SendRemoteQueryInternal has fired off the query and we call OnComplete in 1.688 + // the nsIStreamListener.onStopRequest. 1.689 + return rv; 1.690 +} 1.691 + 1.692 +nsresult 1.693 +PendingLookup::SendRemoteQueryInternal() 1.694 +{ 1.695 + LOG(("Sending remote query for application reputation [this = %p]", this)); 1.696 + // We did not find a local result, so fire off the query to the application 1.697 + // reputation service. 1.698 + safe_browsing::ClientDownloadRequest req; 1.699 + nsCOMPtr<nsIURI> uri; 1.700 + nsresult rv; 1.701 + rv = mQuery->GetSourceURI(getter_AddRefs(uri)); 1.702 + NS_ENSURE_SUCCESS(rv, rv); 1.703 + nsCString spec; 1.704 + rv = uri->GetSpec(spec); 1.705 + NS_ENSURE_SUCCESS(rv, rv); 1.706 + req.set_url(spec.get()); 1.707 + 1.708 + uint32_t fileSize; 1.709 + rv = mQuery->GetFileSize(&fileSize); 1.710 + NS_ENSURE_SUCCESS(rv, rv); 1.711 + req.set_length(fileSize); 1.712 + // We have no way of knowing whether or not a user initiated the download. 1.713 + req.set_user_initiated(false); 1.714 + 1.715 + nsCString locale; 1.716 + NS_ENSURE_SUCCESS(Preferences::GetCString(PREF_GENERAL_LOCALE, &locale), 1.717 + NS_ERROR_NOT_AVAILABLE); 1.718 + req.set_locale(locale.get()); 1.719 + nsCString sha256Hash; 1.720 + rv = mQuery->GetSha256Hash(sha256Hash); 1.721 + NS_ENSURE_SUCCESS(rv, rv); 1.722 + req.mutable_digests()->set_sha256(sha256Hash.Data()); 1.723 + nsString fileName; 1.724 + rv = mQuery->GetSuggestedFileName(fileName); 1.725 + NS_ENSURE_SUCCESS(rv, rv); 1.726 + req.set_file_basename(NS_ConvertUTF16toUTF8(fileName).get()); 1.727 + req.mutable_signature()->CopyFrom(mSignatureInfo); 1.728 + 1.729 + if (req.signature().trusted()) { 1.730 + LOG(("Got signed binary for remote application reputation check " 1.731 + "[this = %p]", this)); 1.732 + } else { 1.733 + LOG(("Got unsigned binary for remote application reputation check " 1.734 + "[this = %p]", this)); 1.735 + } 1.736 + 1.737 + // Serialize the protocol buffer to a string. This can only fail if we are 1.738 + // out of memory, or if the protocol buffer req is missing required fields 1.739 + // (only the URL for now). 1.740 + std::string serialized; 1.741 + if (!req.SerializeToString(&serialized)) { 1.742 + return NS_ERROR_UNEXPECTED; 1.743 + } 1.744 + 1.745 + // Set the input stream to the serialized protocol buffer 1.746 + nsCOMPtr<nsIStringInputStream> sstream = 1.747 + do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv); 1.748 + NS_ENSURE_SUCCESS(rv, rv); 1.749 + 1.750 + rv = sstream->SetData(serialized.c_str(), serialized.length()); 1.751 + NS_ENSURE_SUCCESS(rv, rv); 1.752 + 1.753 + // Set up the channel to transmit the request to the service. 1.754 + nsCOMPtr<nsIChannel> channel; 1.755 + nsCString serviceUrl; 1.756 + NS_ENSURE_SUCCESS(Preferences::GetCString(PREF_SB_APP_REP_URL, &serviceUrl), 1.757 + NS_ERROR_NOT_AVAILABLE); 1.758 + nsCOMPtr<nsIIOService> ios = do_GetService(NS_IOSERVICE_CONTRACTID, &rv); 1.759 + rv = ios->NewChannel(serviceUrl, nullptr, nullptr, getter_AddRefs(channel)); 1.760 + NS_ENSURE_SUCCESS(rv, rv); 1.761 + 1.762 + nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel, &rv)); 1.763 + NS_ENSURE_SUCCESS(rv, rv); 1.764 + 1.765 + // Upload the protobuf to the application reputation service. 1.766 + nsCOMPtr<nsIUploadChannel2> uploadChannel = do_QueryInterface(channel, &rv); 1.767 + NS_ENSURE_SUCCESS(rv, rv); 1.768 + 1.769 + rv = uploadChannel->ExplicitSetUploadStream(sstream, 1.770 + NS_LITERAL_CSTRING("application/octet-stream"), serialized.size(), 1.771 + NS_LITERAL_CSTRING("POST"), false); 1.772 + NS_ENSURE_SUCCESS(rv, rv); 1.773 + 1.774 + // Set the Safebrowsing cookie jar, so that the regular Google cookie is not 1.775 + // sent with this request. See bug 897516. 1.776 + nsCOMPtr<nsIInterfaceRequestor> loadContext = 1.777 + new mozilla::LoadContext(NECKO_SAFEBROWSING_APP_ID); 1.778 + rv = channel->SetNotificationCallbacks(loadContext); 1.779 + NS_ENSURE_SUCCESS(rv, rv); 1.780 + 1.781 + rv = channel->AsyncOpen(this, nullptr); 1.782 + NS_ENSURE_SUCCESS(rv, rv); 1.783 + 1.784 + return NS_OK; 1.785 +} 1.786 + 1.787 +//////////////////////////////////////////////////////////////////////////////// 1.788 +//// nsIStreamListener 1.789 +static NS_METHOD 1.790 +AppendSegmentToString(nsIInputStream* inputStream, 1.791 + void *closure, 1.792 + const char *rawSegment, 1.793 + uint32_t toOffset, 1.794 + uint32_t count, 1.795 + uint32_t *writeCount) { 1.796 + nsAutoCString* decodedData = static_cast<nsAutoCString*>(closure); 1.797 + decodedData->Append(rawSegment, count); 1.798 + *writeCount = count; 1.799 + return NS_OK; 1.800 +} 1.801 + 1.802 +NS_IMETHODIMP 1.803 +PendingLookup::OnDataAvailable(nsIRequest *aRequest, 1.804 + nsISupports *aContext, 1.805 + nsIInputStream *aStream, 1.806 + uint64_t offset, 1.807 + uint32_t count) { 1.808 + uint32_t read; 1.809 + return aStream->ReadSegments(AppendSegmentToString, &mResponse, count, &read); 1.810 +} 1.811 + 1.812 +NS_IMETHODIMP 1.813 +PendingLookup::OnStartRequest(nsIRequest *aRequest, 1.814 + nsISupports *aContext) { 1.815 + return NS_OK; 1.816 +} 1.817 + 1.818 +NS_IMETHODIMP 1.819 +PendingLookup::OnStopRequest(nsIRequest *aRequest, 1.820 + nsISupports *aContext, 1.821 + nsresult aResult) { 1.822 + NS_ENSURE_STATE(mCallback); 1.823 + 1.824 + bool shouldBlock = false; 1.825 + nsresult rv = OnStopRequestInternal(aRequest, aContext, aResult, 1.826 + &shouldBlock); 1.827 + OnComplete(shouldBlock, rv); 1.828 + return rv; 1.829 +} 1.830 + 1.831 +nsresult 1.832 +PendingLookup::OnStopRequestInternal(nsIRequest *aRequest, 1.833 + nsISupports *aContext, 1.834 + nsresult aResult, 1.835 + bool* aShouldBlock) { 1.836 + if (NS_FAILED(aResult)) { 1.837 + Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_SERVER, 1.838 + SERVER_RESPONSE_FAILED); 1.839 + return aResult; 1.840 + } 1.841 + 1.842 + *aShouldBlock = false; 1.843 + nsresult rv; 1.844 + nsCOMPtr<nsIHttpChannel> channel = do_QueryInterface(aRequest, &rv); 1.845 + if (NS_FAILED(rv)) { 1.846 + Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_SERVER, 1.847 + SERVER_RESPONSE_FAILED); 1.848 + return rv; 1.849 + } 1.850 + 1.851 + uint32_t status = 0; 1.852 + rv = channel->GetResponseStatus(&status); 1.853 + if (NS_FAILED(rv)) { 1.854 + Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_SERVER, 1.855 + SERVER_RESPONSE_FAILED); 1.856 + return rv; 1.857 + } 1.858 + 1.859 + if (status != 200) { 1.860 + Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_SERVER, 1.861 + SERVER_RESPONSE_FAILED); 1.862 + return NS_ERROR_NOT_AVAILABLE; 1.863 + } 1.864 + 1.865 + std::string buf(mResponse.Data(), mResponse.Length()); 1.866 + safe_browsing::ClientDownloadResponse response; 1.867 + if (!response.ParseFromString(buf)) { 1.868 + NS_WARNING("Could not parse protocol buffer"); 1.869 + Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_SERVER, 1.870 + SERVER_RESPONSE_INVALID); 1.871 + return NS_ERROR_CANNOT_CONVERT_DATA; 1.872 + } 1.873 + 1.874 + // There are several more verdicts, but we only respect one for now and treat 1.875 + // everything else as SAFE. 1.876 + Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_SERVER, 1.877 + SERVER_RESPONSE_VALID); 1.878 + if (response.verdict() == safe_browsing::ClientDownloadResponse::DANGEROUS) { 1.879 + *aShouldBlock = true; 1.880 + } 1.881 + 1.882 + return NS_OK; 1.883 +} 1.884 + 1.885 +NS_IMPL_ISUPPORTS(ApplicationReputationService, 1.886 + nsIApplicationReputationService) 1.887 + 1.888 +ApplicationReputationService* 1.889 + ApplicationReputationService::gApplicationReputationService = nullptr; 1.890 + 1.891 +ApplicationReputationService* 1.892 +ApplicationReputationService::GetSingleton() 1.893 +{ 1.894 + if (gApplicationReputationService) { 1.895 + NS_ADDREF(gApplicationReputationService); 1.896 + return gApplicationReputationService; 1.897 + } 1.898 + 1.899 + // We're not initialized yet. 1.900 + gApplicationReputationService = new ApplicationReputationService(); 1.901 + if (gApplicationReputationService) { 1.902 + NS_ADDREF(gApplicationReputationService); 1.903 + } 1.904 + 1.905 + return gApplicationReputationService; 1.906 +} 1.907 + 1.908 +ApplicationReputationService::ApplicationReputationService() 1.909 +{ 1.910 +#if defined(PR_LOGGING) 1.911 + if (!prlog) { 1.912 + prlog = PR_NewLogModule("ApplicationReputation"); 1.913 + } 1.914 +#endif 1.915 + LOG(("Application reputation service started up")); 1.916 +} 1.917 + 1.918 +ApplicationReputationService::~ApplicationReputationService() { 1.919 + LOG(("Application reputation service shutting down")); 1.920 +} 1.921 + 1.922 +NS_IMETHODIMP 1.923 +ApplicationReputationService::QueryReputation( 1.924 + nsIApplicationReputationQuery* aQuery, 1.925 + nsIApplicationReputationCallback* aCallback) { 1.926 + NS_ENSURE_ARG_POINTER(aQuery); 1.927 + NS_ENSURE_ARG_POINTER(aCallback); 1.928 + 1.929 + Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_COUNT, true); 1.930 + nsresult rv = QueryReputationInternal(aQuery, aCallback); 1.931 + if (NS_FAILED(rv)) { 1.932 + Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_SHOULD_BLOCK, 1.933 + false); 1.934 + aCallback->OnComplete(false, rv); 1.935 + } 1.936 + return NS_OK; 1.937 +} 1.938 + 1.939 +nsresult ApplicationReputationService::QueryReputationInternal( 1.940 + nsIApplicationReputationQuery* aQuery, 1.941 + nsIApplicationReputationCallback* aCallback) { 1.942 + nsresult rv; 1.943 + // If malware checks aren't enabled, don't query application reputation. 1.944 + if (!Preferences::GetBool(PREF_SB_MALWARE_ENABLED, false)) { 1.945 + return NS_ERROR_NOT_AVAILABLE; 1.946 + } 1.947 + 1.948 + // If there is no service URL for querying application reputation, abort. 1.949 + nsCString serviceUrl; 1.950 + NS_ENSURE_SUCCESS(Preferences::GetCString(PREF_SB_APP_REP_URL, &serviceUrl), 1.951 + NS_ERROR_NOT_AVAILABLE); 1.952 + if (serviceUrl.EqualsLiteral("")) { 1.953 + return NS_ERROR_NOT_AVAILABLE; 1.954 + } 1.955 + 1.956 + nsCOMPtr<nsIURI> uri; 1.957 + rv = aQuery->GetSourceURI(getter_AddRefs(uri)); 1.958 + NS_ENSURE_SUCCESS(rv, rv); 1.959 + // Bail if the URI hasn't been set. 1.960 + NS_ENSURE_STATE(uri); 1.961 + 1.962 + // Create a new pending lookup and start the call chain. 1.963 + nsRefPtr<PendingLookup> lookup(new PendingLookup(aQuery, aCallback)); 1.964 + NS_ENSURE_STATE(lookup); 1.965 + 1.966 + return lookup->StartLookup(); 1.967 +}