Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
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 }