|
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" |
|
11 |
|
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" |
|
27 |
|
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" |
|
33 |
|
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" |
|
45 |
|
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; |
|
52 |
|
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" |
|
59 |
|
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 |
|
69 |
|
70 class PendingDBLookup; |
|
71 |
|
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 |
|
82 |
|
83 // Constructor and destructor. |
|
84 PendingLookup(nsIApplicationReputationQuery* aQuery, |
|
85 nsIApplicationReputationCallback* aCallback); |
|
86 ~PendingLookup(); |
|
87 |
|
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(); |
|
94 |
|
95 private: |
|
96 friend class PendingDBLookup; |
|
97 |
|
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 }; |
|
105 |
|
106 // The query containing metadata about the downloaded file. |
|
107 nsCOMPtr<nsIApplicationReputationQuery> mQuery; |
|
108 |
|
109 // The callback with which to report the verdict. |
|
110 nsCOMPtr<nsIApplicationReputationCallback> mCallback; |
|
111 |
|
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; |
|
117 |
|
118 // When we started this query |
|
119 TimeStamp mStartTime; |
|
120 |
|
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; |
|
124 |
|
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; |
|
129 |
|
130 // Returns true if the file is likely to be binary on Windows. |
|
131 bool IsBinaryFile(); |
|
132 |
|
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); |
|
136 |
|
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); |
|
143 |
|
144 // Escape '/' and '%' in certificate attribute values. |
|
145 nsCString EscapeCertificateAttribute(const nsACString& aAttribute); |
|
146 |
|
147 // Escape ':' in fingerprint values. |
|
148 nsCString EscapeFingerprint(const nsACString& aAttribute); |
|
149 |
|
150 // Generate whitelist strings for the given certificate pair from the same |
|
151 // certificate chain. |
|
152 nsresult GenerateWhitelistStringsForPair( |
|
153 nsIX509Cert* certificate, nsIX509Cert* issuer); |
|
154 |
|
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); |
|
159 |
|
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); |
|
167 |
|
168 // Parse the XPCOM certificate lists and stick them into the protocol buffer |
|
169 // version. |
|
170 nsresult ParseCertificates(nsIArray* aSigArray, |
|
171 ClientDownloadRequest_SignatureInfo* aSigInfo); |
|
172 |
|
173 // Helper function to ensure that we call PendingLookup::LookupNext or |
|
174 // PendingLookup::OnComplete. |
|
175 nsresult DoLookupInternal(); |
|
176 |
|
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(); |
|
182 |
|
183 // Sends a query to the remote application reputation service. Returns NS_OK |
|
184 // on success. |
|
185 nsresult SendRemoteQuery(); |
|
186 |
|
187 // Helper function to ensure that we always call the callback. |
|
188 nsresult SendRemoteQueryInternal(); |
|
189 }; |
|
190 |
|
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 |
|
198 |
|
199 // Constructor and destructor |
|
200 PendingDBLookup(PendingLookup* aPendingLookup); |
|
201 ~PendingDBLookup(); |
|
202 |
|
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 }; |
|
215 |
|
216 nsCString mSpec; |
|
217 bool mAllowlistOnly; |
|
218 nsRefPtr<PendingLookup> mPendingLookup; |
|
219 nsresult LookupSpecInternal(const nsACString& aSpec); |
|
220 }; |
|
221 |
|
222 NS_IMPL_ISUPPORTS(PendingDBLookup, |
|
223 nsIUrlClassifierCallback) |
|
224 |
|
225 PendingDBLookup::PendingDBLookup(PendingLookup* aPendingLookup) : |
|
226 mAllowlistOnly(false), |
|
227 mPendingLookup(aPendingLookup) |
|
228 { |
|
229 LOG(("Created pending DB lookup [this = %p]", this)); |
|
230 } |
|
231 |
|
232 PendingDBLookup::~PendingDBLookup() |
|
233 { |
|
234 LOG(("Destroying pending DB lookup [this = %p]", this)); |
|
235 mPendingLookup = nullptr; |
|
236 } |
|
237 |
|
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 } |
|
254 |
|
255 nsresult |
|
256 PendingDBLookup::LookupSpecInternal(const nsACString& aSpec) |
|
257 { |
|
258 nsresult rv; |
|
259 |
|
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); |
|
264 |
|
265 nsCOMPtr<nsIPrincipal> principal; |
|
266 nsCOMPtr<nsIScriptSecurityManager> secMan = |
|
267 do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv); |
|
268 NS_ENSURE_SUCCESS(rv, rv); |
|
269 |
|
270 rv = secMan->GetNoAppCodebasePrincipal(uri, getter_AddRefs(principal)); |
|
271 NS_ENSURE_SUCCESS(rv, rv); |
|
272 |
|
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 } |
|
292 |
|
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 } |
|
307 |
|
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 } |
|
315 |
|
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 } |
|
320 |
|
321 NS_IMPL_ISUPPORTS(PendingLookup, |
|
322 nsIStreamListener, |
|
323 nsIRequestObserver) |
|
324 |
|
325 PendingLookup::PendingLookup(nsIApplicationReputationQuery* aQuery, |
|
326 nsIApplicationReputationCallback* aCallback) : |
|
327 mQuery(aQuery), |
|
328 mCallback(aCallback) |
|
329 { |
|
330 LOG(("Created pending lookup [this = %p]", this)); |
|
331 } |
|
332 |
|
333 PendingLookup::~PendingLookup() |
|
334 { |
|
335 LOG(("Destroying pending lookup [this = %p]", this)); |
|
336 } |
|
337 |
|
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 } |
|
365 |
|
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 } |
|
409 |
|
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 } |
|
429 |
|
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 } |
|
443 |
|
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/"); |
|
457 |
|
458 nsString fingerprint; |
|
459 nsresult rv = issuer->GetSha1Fingerprint(fingerprint); |
|
460 NS_ENSURE_SUCCESS(rv, rv); |
|
461 whitelistString.Append( |
|
462 EscapeFingerprint(NS_ConvertUTF16toUTF8(fingerprint))); |
|
463 |
|
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 } |
|
472 |
|
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 } |
|
481 |
|
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())); |
|
491 |
|
492 mAllowlistSpecs.AppendElement(whitelistString); |
|
493 return NS_OK; |
|
494 } |
|
495 |
|
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 } |
|
505 |
|
506 // Get the signer. |
|
507 nsresult rv; |
|
508 nsCOMPtr<nsIX509CertDB> certDB = do_GetService(NS_X509CERTDB_CONTRACTID, &rv); |
|
509 NS_ENSURE_SUCCESS(rv, rv); |
|
510 |
|
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); |
|
516 |
|
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); |
|
524 |
|
525 nsresult rv = GenerateWhitelistStringsForPair(signer, issuer); |
|
526 NS_ENSURE_SUCCESS(rv, rv); |
|
527 } |
|
528 return NS_OK; |
|
529 } |
|
530 |
|
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 } |
|
542 |
|
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 } |
|
553 |
|
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); |
|
561 |
|
562 nsCString spec; |
|
563 rv = uri->GetSpec(spec); |
|
564 NS_ENSURE_SUCCESS(rv, rv); |
|
565 mAnylistSpecs.AppendElement(spec); |
|
566 |
|
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 } |
|
575 |
|
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); |
|
581 |
|
582 if (sigArray) { |
|
583 rv = ParseCertificates(sigArray, &mSignatureInfo); |
|
584 NS_ENSURE_SUCCESS(rv, rv); |
|
585 } |
|
586 |
|
587 rv = GenerateWhitelistStrings(mSignatureInfo); |
|
588 NS_ENSURE_SUCCESS(rv, rv); |
|
589 |
|
590 // Start the call chain. |
|
591 return LookupNext(); |
|
592 } |
|
593 |
|
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 } |
|
611 |
|
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); |
|
619 |
|
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); |
|
626 |
|
627 bool hasMoreChains = false; |
|
628 rv = chains->HasMoreElements(&hasMoreChains); |
|
629 NS_ENSURE_SUCCESS(rv, rv); |
|
630 |
|
631 while (hasMoreChains) { |
|
632 nsCOMPtr<nsISupports> supports; |
|
633 rv = chains->GetNext(getter_AddRefs(supports)); |
|
634 NS_ENSURE_SUCCESS(rv, rv); |
|
635 |
|
636 nsCOMPtr<nsIX509CertList> certList = do_QueryInterface(supports, &rv); |
|
637 NS_ENSURE_SUCCESS(rv, rv); |
|
638 |
|
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); |
|
644 |
|
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); |
|
652 |
|
653 nsCOMPtr<nsIX509Cert> cert = do_QueryInterface(supports, &rv); |
|
654 NS_ENSURE_SUCCESS(rv, rv); |
|
655 |
|
656 uint8_t* data = nullptr; |
|
657 uint32_t len = 0; |
|
658 rv = cert->GetRawDER(&len, &data); |
|
659 NS_ENSURE_SUCCESS(rv, rv); |
|
660 |
|
661 // Add this certificate to the protobuf to send remotely. |
|
662 certChain->add_element()->set_certificate(data, len); |
|
663 nsMemory::Free(data); |
|
664 |
|
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 } |
|
676 |
|
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 } |
|
688 |
|
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()); |
|
704 |
|
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); |
|
711 |
|
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); |
|
725 |
|
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 } |
|
733 |
|
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 } |
|
741 |
|
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); |
|
746 |
|
747 rv = sstream->SetData(serialized.c_str(), serialized.length()); |
|
748 NS_ENSURE_SUCCESS(rv, rv); |
|
749 |
|
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); |
|
758 |
|
759 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel, &rv)); |
|
760 NS_ENSURE_SUCCESS(rv, rv); |
|
761 |
|
762 // Upload the protobuf to the application reputation service. |
|
763 nsCOMPtr<nsIUploadChannel2> uploadChannel = do_QueryInterface(channel, &rv); |
|
764 NS_ENSURE_SUCCESS(rv, rv); |
|
765 |
|
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); |
|
770 |
|
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); |
|
777 |
|
778 rv = channel->AsyncOpen(this, nullptr); |
|
779 NS_ENSURE_SUCCESS(rv, rv); |
|
780 |
|
781 return NS_OK; |
|
782 } |
|
783 |
|
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 } |
|
798 |
|
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 } |
|
808 |
|
809 NS_IMETHODIMP |
|
810 PendingLookup::OnStartRequest(nsIRequest *aRequest, |
|
811 nsISupports *aContext) { |
|
812 return NS_OK; |
|
813 } |
|
814 |
|
815 NS_IMETHODIMP |
|
816 PendingLookup::OnStopRequest(nsIRequest *aRequest, |
|
817 nsISupports *aContext, |
|
818 nsresult aResult) { |
|
819 NS_ENSURE_STATE(mCallback); |
|
820 |
|
821 bool shouldBlock = false; |
|
822 nsresult rv = OnStopRequestInternal(aRequest, aContext, aResult, |
|
823 &shouldBlock); |
|
824 OnComplete(shouldBlock, rv); |
|
825 return rv; |
|
826 } |
|
827 |
|
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 } |
|
838 |
|
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 } |
|
847 |
|
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 } |
|
855 |
|
856 if (status != 200) { |
|
857 Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_SERVER, |
|
858 SERVER_RESPONSE_FAILED); |
|
859 return NS_ERROR_NOT_AVAILABLE; |
|
860 } |
|
861 |
|
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 } |
|
870 |
|
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 } |
|
878 |
|
879 return NS_OK; |
|
880 } |
|
881 |
|
882 NS_IMPL_ISUPPORTS(ApplicationReputationService, |
|
883 nsIApplicationReputationService) |
|
884 |
|
885 ApplicationReputationService* |
|
886 ApplicationReputationService::gApplicationReputationService = nullptr; |
|
887 |
|
888 ApplicationReputationService* |
|
889 ApplicationReputationService::GetSingleton() |
|
890 { |
|
891 if (gApplicationReputationService) { |
|
892 NS_ADDREF(gApplicationReputationService); |
|
893 return gApplicationReputationService; |
|
894 } |
|
895 |
|
896 // We're not initialized yet. |
|
897 gApplicationReputationService = new ApplicationReputationService(); |
|
898 if (gApplicationReputationService) { |
|
899 NS_ADDREF(gApplicationReputationService); |
|
900 } |
|
901 |
|
902 return gApplicationReputationService; |
|
903 } |
|
904 |
|
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 } |
|
914 |
|
915 ApplicationReputationService::~ApplicationReputationService() { |
|
916 LOG(("Application reputation service shutting down")); |
|
917 } |
|
918 |
|
919 NS_IMETHODIMP |
|
920 ApplicationReputationService::QueryReputation( |
|
921 nsIApplicationReputationQuery* aQuery, |
|
922 nsIApplicationReputationCallback* aCallback) { |
|
923 NS_ENSURE_ARG_POINTER(aQuery); |
|
924 NS_ENSURE_ARG_POINTER(aCallback); |
|
925 |
|
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 } |
|
935 |
|
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 } |
|
944 |
|
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 } |
|
952 |
|
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); |
|
958 |
|
959 // Create a new pending lookup and start the call chain. |
|
960 nsRefPtr<PendingLookup> lookup(new PendingLookup(aQuery, aCallback)); |
|
961 NS_ENSURE_STATE(lookup); |
|
962 |
|
963 return lookup->StartLookup(); |
|
964 } |