Bug 964465: Check certificate whitelist strings before sending remote lookup (r=gcp)

This commit is contained in:
Monica Chew 2014-02-19 15:14:02 -08:00
Родитель be50b2c3c3
Коммит 4e6ac0bad5
7 изменённых файлов: 803 добавлений и 221 удалений

Просмотреть файл

@ -3,7 +3,9 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// See
// https://wiki.mozilla.org/Security/Features/Application_Reputation_Design_Doc
// for a description of Chrome's implementation of this feature.
#include "ApplicationReputation.h"
#include "csd.pb.h"
@ -20,12 +22,15 @@
#include "nsIURI.h"
#include "nsIUrlClassifierDBService.h"
#include "nsIX509Cert.h"
#include "nsIX509CertDB.h"
#include "nsIX509CertList.h"
#include "mozilla/Preferences.h"
#include "mozilla/Services.h"
#include "mozilla/Telemetry.h"
#include "mozilla/TimeStamp.h"
#include "nsAutoPtr.h"
#include "nsCOMPtr.h"
#include "nsDebug.h"
#include "nsError.h"
@ -33,12 +38,15 @@
#include "nsReadableUtils.h"
#include "nsServiceManagerUtils.h"
#include "nsString.h"
#include "nsTArray.h"
#include "nsThreadUtils.h"
#include "nsXPCOMStrings.h"
using mozilla::Preferences;
using mozilla::Telemetry::Accumulate;
using safe_browsing::ClientDownloadRequest;
using safe_browsing::ClientDownloadRequest_SignatureInfo;
using safe_browsing::ClientDownloadRequest_CertificateChain;
// Preferences that we need to initialize the query. We may need another
// preference than browser.safebrowsing.malware.enabled, or simply use
@ -58,84 +66,245 @@ PRLogModuleInfo *ApplicationReputationService::prlog = nullptr;
#define LOG(args)
#define LOG_ENABLED() (false)
#endif
/**
* Keep track of pending lookups. Once the ApplicationReputationService creates
* this, it is guaranteed to call mCallback. This class is private to
* ApplicationReputationService.
*/
class PendingLookup MOZ_FINAL :
public nsIStreamListener,
public nsIUrlClassifierCallback {
class PendingDBLookup;
// A single use class private to ApplicationReputationService encapsulating an
// nsIApplicationReputationQuery and an nsIApplicationReputationCallback. Once
// created by ApplicationReputationService, it is guaranteed to call mCallback.
// This class is private to ApplicationReputationService.
class PendingLookup MOZ_FINAL : public nsIStreamListener
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIREQUESTOBSERVER
NS_DECL_NSISTREAMLISTENER
NS_DECL_NSIURLCLASSIFIERCALLBACK
// Constructor and destructor.
PendingLookup(nsIApplicationReputationQuery* aQuery,
nsIApplicationReputationCallback* aCallback);
~PendingLookup();
// Start the lookup. The lookup may have 2 parts: local and remote. In the
// local lookup, PendingDBLookups are created to query the local allow and
// blocklists for various URIs associated with this downloaded file. In the
// event that no results are found, a remote lookup is sent to the Application
// Reputation server.
nsresult StartLookup();
private:
/**
* Telemetry states.
*/
/**
* The download appeared on the allowlist, blocklist, or no list (and thus
* could trigger a remote query).
*/
enum LIST_TYPES {
ALLOW_LIST = 0,
BLOCK_LIST = 1,
NO_LIST = 2,
};
/**
* Status of the remote response (valid or not).
*/
friend class PendingDBLookup;
// Telemetry states.
// Status of the remote response (valid or not).
enum SERVER_RESPONSE_TYPES {
SERVER_RESPONSE_VALID = 0,
SERVER_RESPONSE_FAILED = 1,
SERVER_RESPONSE_INVALID = 2,
};
// The query containing metadata about the downloaded file.
nsCOMPtr<nsIApplicationReputationQuery> mQuery;
// The callback with which to report the verdict.
nsCOMPtr<nsIApplicationReputationCallback> mCallback;
/**
* The response from the application reputation query. This is read in chunks
* as part of our nsIStreamListener implementation and may contain embedded
* NULLs.
*/
// An array of strings created from certificate information used to whitelist
// the downloaded file.
nsTArray<nsCString> mAllowlistSpecs;
// When we started this query
TimeStamp mStartTime;
// The protocol buffer used to store signature information extracted using
// the Windows Authenticode API, if the binary is signed.
ClientDownloadRequest_SignatureInfo mSignatureInfo;
// The response from the application reputation query. This is read in chunks
// as part of our nsIStreamListener implementation and may contain embedded
// NULLs.
nsCString mResponse;
/**
* Clean up and call the callback. PendingLookup must not be used after this
* function is called.
*/
// Clean up and call the callback. PendingLookup must not be used after this
// function is called.
nsresult OnComplete(bool shouldBlock, nsresult rv);
/**
* Wrapper function for nsIStreamListener.onStopRequest to make it easy to
* guarantee calling the callback
*/
// Wrapper function for nsIStreamListener.onStopRequest to make it easy to
// guarantee calling the callback
nsresult OnStopRequestInternal(nsIRequest *aRequest,
nsISupports *aContext,
nsresult aResult,
bool* aShouldBlock);
/**
* Parse the XPCOM certificate lists and stick them into the protocol buffer
* version.
*/
// Escape '/' and '%' in certificate attribute values.
nsCString EscapeCertificateAttribute(const nsACString& aAttribute);
// Escape ':' in fingerprint values.
nsCString EscapeFingerprint(const nsACString& aAttribute);
// Generate whitelist strings for the given certificate pair from the same
// certificate chain.
nsresult GenerateWhitelistStringsForPair(
nsIX509Cert* certificate, nsIX509Cert* issuer);
// Generate whitelist strings for the given certificate chain, which starts
// with the signer and may go all the way to the root cert.
nsresult GenerateWhitelistStringsForChain(
const ClientDownloadRequest_CertificateChain& aChain);
// For signed binaries, generate strings of the form:
// http://sb-ssl.google.com/safebrowsing/csd/certificate/
// <issuer_cert_sha1_fingerprint>[/CN=<cn>][/O=<org>][/OU=<unit>]
// for each (cert, issuer) pair in each chain of certificates that is
// associated with the binary.
nsresult GenerateWhitelistStrings(
const ClientDownloadRequest_SignatureInfo& aSignatureInfo);
// Parse the XPCOM certificate lists and stick them into the protocol buffer
// version.
nsresult ParseCertificates(nsIArray* aSigArray,
ClientDownloadRequest_SignatureInfo* aSigInfo);
/**
* Sends a query to the remote application reputation service. Returns NS_OK
* on success.
*/
// Helper function to ensure that we call PendingLookup::LookupNext or
// PendingLookup::OnComplete.
nsresult DoLookupInternal();
// Looks up all the URIs that may be responsible for allowlisting or
// blocklisting the downloaded file. These URIs may include whitelist strings
// generated by certificates verifying the binary as well as the target URI
// from which the file was downloaded.
nsresult LookupNext();
// Sends a query to the remote application reputation service. Returns NS_OK
// on success.
nsresult SendRemoteQuery();
// Helper function to ensure that we always call the callback.
nsresult SendRemoteQueryInternal();
};
NS_IMPL_ISUPPORTS3(PendingLookup,
nsIStreamListener,
nsIRequestObserver,
// A single-use class for looking up a single URI in the safebrowsing DB. This
// class is private to PendingLookup.
class PendingDBLookup MOZ_FINAL : public nsIUrlClassifierCallback
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIURLCLASSIFIERCALLBACK
// Constructor and destructor
PendingDBLookup(PendingLookup* aPendingLookup);
~PendingDBLookup();
// Look up the given URI in the safebrowsing DBs, optionally on both the allow
// list and the blocklist. If there is a match, call
// PendingLookup::OnComplete. Otherwise, call PendingLookup::LookupNext.
nsresult LookupSpec(const nsACString& aSpec, bool aAllowListOnly);
private:
// The download appeared on the allowlist, blocklist, or no list (and thus
// could trigger a remote query.
enum LIST_TYPES {
ALLOW_LIST = 0,
BLOCK_LIST = 1,
NO_LIST = 2,
};
nsCString mSpec;
bool mAllowListOnly;
nsRefPtr<PendingLookup> mPendingLookup;
nsresult LookupSpecInternal(const nsACString& aSpec);
};
NS_IMPL_ISUPPORTS1(PendingDBLookup,
nsIUrlClassifierCallback)
PendingDBLookup::PendingDBLookup(PendingLookup* aPendingLookup) :
mAllowListOnly(false),
mPendingLookup(aPendingLookup)
{
LOG(("Created pending DB lookup [this = %p]", this));
}
PendingDBLookup::~PendingDBLookup()
{
LOG(("Destroying pending DB lookup [this = %p]", this));
mPendingLookup = nullptr;
}
nsresult
PendingDBLookup::LookupSpec(const nsACString& aSpec,
bool aAllowListOnly)
{
LOG(("Checking principal %s", aSpec.Data()));
mSpec = aSpec;
mAllowListOnly = aAllowListOnly;
nsresult rv = LookupSpecInternal(aSpec);
if (NS_FAILED(rv)) {
LOG(("Error in LookupSpecInternal"));
return mPendingLookup->OnComplete(false, NS_OK);
}
// LookupSpecInternal has called nsIUrlClassifierCallback.lookup, which is
// guaranteed to call HandleEvent.
return rv;
}
nsresult
PendingDBLookup::LookupSpecInternal(const nsACString& aSpec)
{
nsresult rv;
nsCOMPtr<nsIURI> uri = nullptr;
nsCOMPtr<nsIIOService> ios = do_GetService(NS_IOSERVICE_CONTRACTID, &rv);
rv = ios->NewURI(aSpec, nullptr, nullptr, getter_AddRefs(uri));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIPrincipal> principal = nullptr;
nsCOMPtr<nsIScriptSecurityManager> secMan =
do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = secMan->GetNoAppCodebasePrincipal(uri, getter_AddRefs(principal));
NS_ENSURE_SUCCESS(rv, rv);
// Check local lists to see if the URI has already been whitelisted or
// blacklisted.
LOG(("Checking DB service for principal %s [this = %p]", mSpec.get(), this));
nsCOMPtr<nsIUrlClassifierDBService> dbService =
do_GetService(NS_URLCLASSIFIERDBSERVICE_CONTRACTID, &rv);
return dbService->Lookup(principal, this);
}
NS_IMETHODIMP
PendingDBLookup::HandleEvent(const nsACString& tables)
{
// HandleEvent is guaranteed to call either:
// 1) PendingLookup::OnComplete if the URL can be classified locally, or
// 2) PendingLookup::LookupNext if the URL can be cannot classified locally.
// Allow listing trumps block listing.
nsAutoCString allowList;
Preferences::GetCString(PREF_DOWNLOAD_ALLOW_TABLE, &allowList);
if (FindInReadable(tables, allowList)) {
Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_LOCAL, ALLOW_LIST);
LOG(("Found principal %s on allowlist [this = %p]", mSpec.get(), this));
return mPendingLookup->OnComplete(false, NS_OK);
}
nsAutoCString blockList;
Preferences::GetCString(PREF_DOWNLOAD_BLOCK_TABLE, &blockList);
if (!mAllowListOnly && FindInReadable(tables, blockList)) {
Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_LOCAL, BLOCK_LIST);
LOG(("Found principal %s on blocklist [this = %p]", mSpec.get(), this));
return mPendingLookup->OnComplete(true, NS_OK);
}
LOG(("Didn't find principal %s on any list [this = %p]", mSpec.get(), this));
Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_LOCAL, NO_LIST);
return mPendingLookup->LookupNext();
}
NS_IMPL_ISUPPORTS2(PendingLookup,
nsIStreamListener,
nsIRequestObserver)
PendingLookup::PendingLookup(nsIApplicationReputationQuery* aQuery,
nsIApplicationReputationCallback* aCallback) :
mQuery(aQuery),
@ -150,45 +319,25 @@ PendingLookup::~PendingLookup()
}
nsresult
PendingLookup::OnComplete(bool shouldBlock, nsresult rv)
PendingLookup::LookupNext()
{
Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_SHOULD_BLOCK,
shouldBlock);
if (shouldBlock) {
LOG(("Application Reputation check failed, blocking bad binary "
"[this = %p]", this));
} else {
LOG(("Application Reputation check passed [this = %p]", this));
// We must call LookupNext or SendRemoteQuery upon return.
// Look up all of the URLs that could whitelist this download.
int index = mAllowlistSpecs.Length() - 1;
if (index >= 0) {
nsCString spec = mAllowlistSpecs[index];
mAllowlistSpecs.RemoveElementAt(index);
nsRefPtr<PendingDBLookup> lookup(new PendingDBLookup(this));
bool allowListOnly = true;
if (index == 0) {
// The last URI is the target URI, which may be used for blacklisting as
// well as whitelisting.
allowListOnly = false;
}
return lookup->LookupSpec(spec, allowListOnly);
}
nsresult res = mCallback->OnComplete(shouldBlock, rv);
return res;
}
////////////////////////////////////////////////////////////////////////////////
//// nsIUrlClassifierCallback
NS_IMETHODIMP
PendingLookup::HandleEvent(const nsACString& tables)
{
// HandleEvent is guaranteed to call the callback if either the URL can be
// classified locally, or if there is an error sending the remote lookup.
// Allow listing trumps block listing.
nsCString allow_list;
Preferences::GetCString(PREF_DOWNLOAD_ALLOW_TABLE, &allow_list);
if (FindInReadable(tables, allow_list)) {
Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_LOCAL, ALLOW_LIST);
LOG(("Found principal on allowlist [this = %p]", this));
return OnComplete(false, NS_OK);
}
nsCString block_list;
Preferences::GetCString(PREF_DOWNLOAD_BLOCK_TABLE, &block_list);
if (FindInReadable(tables, block_list)) {
Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_LOCAL, BLOCK_LIST);
LOG(("Found principal on blocklist [this = %p]", this));
return OnComplete(true, NS_OK);
}
Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_LOCAL, NO_LIST);
// There are no more URIs to check against local list, so send the remote
// query if we can.
// Revert to just ifdef XP_WIN when remote lookups are enabled (bug 933432)
#if 0
nsresult rv = SendRemoteQuery();
@ -201,13 +350,209 @@ PendingLookup::HandleEvent(const nsACString& tables)
#endif
}
nsCString
PendingLookup::EscapeCertificateAttribute(const nsACString& aAttribute)
{
// Escape '/' because it's a field separator, and '%' because Chrome does
nsCString escaped;
escaped.SetCapacity(aAttribute.Length());
for (unsigned int i = 0; i < aAttribute.Length(); ++i) {
if (aAttribute.Data()[i] == '%') {
escaped.Append("%25");
} else if (aAttribute.Data()[i] == '/') {
escaped.Append("%2F");
} else if (aAttribute.Data()[i] == ' ') {
escaped.Append("%20");
} else {
escaped.Append(aAttribute.Data()[i]);
}
}
return escaped;
}
nsCString
PendingLookup::EscapeFingerprint(const nsACString& aFingerprint)
{
// Google's fingerprint doesn't have colons
nsCString escaped;
escaped.SetCapacity(aFingerprint.Length());
for (unsigned int i = 0; i < aFingerprint.Length(); ++i) {
if (aFingerprint.Data()[i] != ':') {
escaped.Append(aFingerprint.Data()[i]);
}
}
return escaped;
}
nsresult
PendingLookup::GenerateWhitelistStringsForPair(
nsIX509Cert* certificate,
nsIX509Cert* issuer)
{
// The whitelist paths have format:
// http://sb-ssl.google.com/safebrowsing/csd/certificate/<issuer_cert_fingerprint>[/CN=<cn>][/O=<org>][/OU=<unit>]
// Any of CN, O, or OU may be omitted from the whitelist entry. Unfortunately
// this is not publicly documented, but the Chrome implementation can be found
// here:
// https://code.google.com/p/chromium/codesearch#search/&q=GetCertificateWhitelistStrings
nsCString whitelistString(
"http://sb-ssl.google.com/safebrowsing/csd/certificate/");
nsString fingerprint;
nsresult rv = issuer->GetSha1Fingerprint(fingerprint);
NS_ENSURE_SUCCESS(rv, rv);
whitelistString.Append(
EscapeFingerprint(NS_ConvertUTF16toUTF8(fingerprint)));
nsString commonName;
rv = certificate->GetCommonName(commonName);
NS_ENSURE_SUCCESS(rv, rv);
if (!commonName.IsEmpty()) {
whitelistString.Append("/CN=");
whitelistString.Append(
EscapeCertificateAttribute(NS_ConvertUTF16toUTF8(commonName)));
}
nsString organization;
rv = certificate->GetOrganization(organization);
NS_ENSURE_SUCCESS(rv, rv);
if (!organization.IsEmpty()) {
whitelistString.Append("/O=");
whitelistString.Append(
EscapeCertificateAttribute(NS_ConvertUTF16toUTF8(organization)));
}
nsString organizationalUnit;
rv = certificate->GetOrganizationalUnit(organizationalUnit);
NS_ENSURE_SUCCESS(rv, rv);
if (!organizationalUnit.IsEmpty()) {
whitelistString.Append("/OU=");
whitelistString.Append(
EscapeCertificateAttribute(NS_ConvertUTF16toUTF8(organizationalUnit)));
}
LOG(("Whitelisting %s", whitelistString.get()));
mAllowlistSpecs.AppendElement(whitelistString);
return NS_OK;
}
nsresult
PendingLookup::GenerateWhitelistStringsForChain(
const safe_browsing::ClientDownloadRequest_CertificateChain& aChain)
{
// We need a signing certificate and an issuer to construct a whitelist
// entry.
if (aChain.element_size() < 2) {
return NS_OK;
}
// Get the signer.
nsresult rv;
nsCOMPtr<nsIX509CertDB> certDB = do_GetService(NS_X509CERTDB_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIX509Cert> signer = nullptr;
rv = certDB->ConstructX509(
const_cast<char *>(aChain.element(0).certificate().data()),
aChain.element(0).certificate().size(), getter_AddRefs(signer));
NS_ENSURE_SUCCESS(rv, rv);
for (int i = 1; i < aChain.element_size(); ++i) {
// Get the issuer.
nsCOMPtr<nsIX509Cert> issuer = nullptr;
rv = certDB->ConstructX509(
const_cast<char *>(aChain.element(i).certificate().data()),
aChain.element(i).certificate().size(), getter_AddRefs(issuer));
NS_ENSURE_SUCCESS(rv, rv);
nsresult rv = GenerateWhitelistStringsForPair(signer, issuer);
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
nsresult
PendingLookup::GenerateWhitelistStrings(
const safe_browsing::ClientDownloadRequest_SignatureInfo& aSignatureInfo)
{
for (int i = 0; i < aSignatureInfo.certificate_chain_size(); ++i) {
nsresult rv = GenerateWhitelistStringsForChain(
aSignatureInfo.certificate_chain(i));
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
nsresult
PendingLookup::StartLookup()
{
mStartTime = TimeStamp::Now();
nsresult rv = DoLookupInternal();
if (NS_FAILED(rv)) {
return OnComplete(false, NS_OK);
};
return rv;
}
nsresult
PendingLookup::DoLookupInternal()
{
// We want to check the target URI against the local lists.
nsCOMPtr<nsIURI> uri = nullptr;
nsresult rv = mQuery->GetSourceURI(getter_AddRefs(uri));
NS_ENSURE_SUCCESS(rv, rv);
nsCString spec;
rv = uri->GetSpec(spec);
NS_ENSURE_SUCCESS(rv, rv);
mAllowlistSpecs.AppendElement(spec);
// Extract the signature and parse certificates so we can use it to check
// whitelists.
nsCOMPtr<nsIArray> sigArray = nullptr;
rv = mQuery->GetSignatureInfo(getter_AddRefs(sigArray));
NS_ENSURE_SUCCESS(rv, rv);
if (sigArray) {
rv = ParseCertificates(sigArray, &mSignatureInfo);
NS_ENSURE_SUCCESS(rv, rv);
}
rv = GenerateWhitelistStrings(mSignatureInfo);
NS_ENSURE_SUCCESS(rv, rv);
// Start the call chain.
return LookupNext();
}
nsresult
PendingLookup::OnComplete(bool shouldBlock, nsresult rv)
{
Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_SHOULD_BLOCK,
shouldBlock);
#if defined(PR_LOGGING)
double t = (TimeStamp::Now() - mStartTime).ToMilliseconds();
#endif
if (shouldBlock) {
LOG(("Application Reputation check failed, blocking bad binary in %f ms "
"[this = %p]", t, this));
} else {
LOG(("Application Reputation check passed in %f ms [this = %p]", t, this));
}
nsresult res = mCallback->OnComplete(shouldBlock, rv);
return res;
}
nsresult
PendingLookup::ParseCertificates(
nsIArray* aSigArray,
ClientDownloadRequest_SignatureInfo* aSignatureInfo)
{
// If we haven't been set for any reason, bail.
NS_ENSURE_ARG_POINTER(aSigArray);
// Binaries may be signed by multiple chains of certificates. If there are no
// chains, the binary is unsiged (or we were unable to extract signature
// chains, the binary is unsigned (or we were unable to extract signature
// information on a non-Windows platform)
nsCOMPtr<nsISimpleEnumerator> chains = nullptr;
nsresult rv = aSigArray->Enumerate(getter_AddRefs(chains));
@ -259,12 +604,24 @@ PendingLookup::ParseCertificates(
nsresult
PendingLookup::SendRemoteQuery()
{
nsresult rv = SendRemoteQueryInternal();
if (NS_FAILED(rv)) {
return OnComplete(false, NS_OK);
}
// SendRemoteQueryInternal has fired off the query and we call OnComplete in
// the nsIStreamListener.onStopRequest.
return rv;
}
nsresult
PendingLookup::SendRemoteQueryInternal()
{
LOG(("Sending remote query for application reputation [this = %p]", this));
// We did not find a local result, so fire off the query to the application
// reputation service.
safe_browsing::ClientDownloadRequest req;
nsCOMPtr<nsIURI> uri;
nsCOMPtr<nsIURI> uri = nullptr;
nsresult rv;
rv = mQuery->GetSourceURI(getter_AddRefs(uri));
NS_ENSURE_SUCCESS(rv, rv);
@ -292,21 +649,14 @@ PendingLookup::SendRemoteQuery()
rv = mQuery->GetSuggestedFileName(fileName);
NS_ENSURE_SUCCESS(rv, rv);
req.set_file_basename(NS_ConvertUTF16toUTF8(fileName).get());
// Extract the signature and parse certificates so we can use it to check
// whitelists.
nsCOMPtr<nsIArray> sigArray = nullptr;
rv = mQuery->GetSignatureInfo(getter_AddRefs(sigArray));
NS_ENSURE_SUCCESS(rv, rv);
// This actually needs to be further up, but it can wait until bug 964465
rv = ParseCertificates(sigArray, req.mutable_signature());
NS_ENSURE_SUCCESS(rv, rv);
req.mutable_signature()->CopyFrom(mSignatureInfo);
if (req.signature().trusted()) {
LOG(("Got signed binary for application reputation [this = %p]", this));
LOG(("Got signed binary for remote application reputation check "
"[this = %p]", this));
} else {
LOG(("Got unsigned binary for application reputation [this = %p]", this));
LOG(("Got unsigned binary for remote application reputation check "
"[this = %p]", this));
}
// Serialize the protocol buffer to a string. This can only fail if we are
@ -325,14 +675,12 @@ PendingLookup::SendRemoteQuery()
rv = sstream->SetData(serialized.c_str(), serialized.length());
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIIOService> ios = do_GetService(NS_IOSERVICE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
// Set up the channel to transmit the request to the service.
nsCOMPtr<nsIChannel> channel;
nsCOMPtr<nsIChannel> channel = nullptr;
nsCString serviceUrl;
NS_ENSURE_SUCCESS(Preferences::GetCString(PREF_SB_APP_REP_URL, &serviceUrl),
NS_ERROR_NOT_AVAILABLE);
nsCOMPtr<nsIIOService> ios = do_GetService(NS_IOSERVICE_CONTRACTID, &rv);
rv = ios->NewChannel(serviceUrl, nullptr, nullptr, getter_AddRefs(channel));
NS_ENSURE_SUCCESS(rv, rv);
@ -475,9 +823,8 @@ ApplicationReputationService::GetSingleton()
return gApplicationReputationService;
}
ApplicationReputationService::ApplicationReputationService() :
mDBService(nullptr),
mSecurityManager(nullptr) {
ApplicationReputationService::ApplicationReputationService()
{
#if defined(PR_LOGGING)
if (!prlog) {
prlog = PR_NewLogModule("ApplicationReputation");
@ -487,6 +834,7 @@ ApplicationReputationService::ApplicationReputationService() :
}
ApplicationReputationService::~ApplicationReputationService() {
LOG(("Application reputation service shutting down"));
}
NS_IMETHODIMP
@ -496,7 +844,6 @@ ApplicationReputationService::QueryReputation(
NS_ENSURE_ARG_POINTER(aQuery);
NS_ENSURE_ARG_POINTER(aCallback);
LOG(("Sending application reputation query"));
Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_COUNT, true);
nsresult rv = QueryReputationInternal(aQuery, aCallback);
if (NS_FAILED(rv)) {
@ -508,17 +855,7 @@ ApplicationReputationService::QueryReputation(
nsresult ApplicationReputationService::QueryReputationInternal(
nsIApplicationReputationQuery* aQuery,
nsIApplicationReputationCallback* aCallback) {
// Lazily instantiate mDBService and mSecurityManager
nsresult rv;
if (!mDBService) {
mDBService = do_GetService(NS_URLCLASSIFIERDBSERVICE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
}
if (!mSecurityManager) {
mSecurityManager = do_GetService("@mozilla.org/scriptsecuritymanager;1",
&rv);
NS_ENSURE_SUCCESS(rv, rv);
}
// If malware checks aren't enabled, don't query application reputation.
if (!Preferences::GetBool(PREF_SB_MALWARE_ENABLED, false)) {
return NS_ERROR_NOT_AVAILABLE;
@ -532,25 +869,15 @@ nsresult ApplicationReputationService::QueryReputationInternal(
return NS_ERROR_NOT_AVAILABLE;
}
// Create a new pending lookup.
nsCOMPtr<nsIURI> uri = nullptr;
rv = aQuery->GetSourceURI(getter_AddRefs(uri));
NS_ENSURE_SUCCESS(rv, rv);
// Bail if the URI hasn't been set.
NS_ENSURE_STATE(uri);
// Create a new pending lookup and start the call chain.
nsRefPtr<PendingLookup> lookup(new PendingLookup(aQuery, aCallback));
NS_ENSURE_STATE(lookup);
nsCOMPtr<nsIURI> uri;
rv = aQuery->GetSourceURI(getter_AddRefs(uri));
NS_ENSURE_SUCCESS(rv, rv);
// If the URI hasn't been set, bail
NS_ENSURE_STATE(uri);
nsCOMPtr<nsIPrincipal> principal;
// In nsIUrlClassifierDBService.lookup, the only use of the principal is to
// wrap the URI. nsISecurityManager.getNoAppCodebasePrincipal is the easiest
// way to wrap a URI inside a principal, since principals can't be
// constructed.
rv = mSecurityManager->GetNoAppCodebasePrincipal(uri,
getter_AddRefs(principal));
NS_ENSURE_SUCCESS(rv, rv);
// Check local lists to see if the URI has already been whitelisted or
// blacklisted.
return mDBService->Lookup(principal, lookup);
return lookup->StartLookup();
}

Просмотреть файл

@ -16,8 +16,7 @@
#include "nsString.h"
class nsIRequest;
class nsIUrlClassifierDBService;
class nsIScriptSecurityManager;
class PendingDBLookup;
class PendingLookup;
class PRLogModuleInfo;
@ -32,6 +31,7 @@ public:
private:
friend class PendingLookup;
friend class PendingDBLookup;
/**
* Global singleton object for holding this factory service.
*/
@ -40,11 +40,6 @@ private:
* NSPR_LOG_MODULES=ApplicationReputation:5
*/
static PRLogModuleInfo* prlog;
/**
* Keeps track of services used to query the local database of URLs.
*/
nsCOMPtr<nsIUrlClassifierDBService> mDBService;
nsCOMPtr<nsIScriptSecurityManager> mSecurityManager;
/**
* This is a singleton, so disallow construction.
*/

Просмотреть файл

@ -1,2 +1,3 @@
a:5:32:32
<EFBFBD><EFBFBD>_H<EFBFBD>^<5E>a<EFBFBD>7<EFBFBD><37>]<5D>=#<23>nm<6E><6D><EFBFBD><EFBFBD>n<EFBFBD><6E>o<EFBFBD><6F>Q<EFBFBD>
a:5:32:64
“Ê_Há^˜aÍ7ÂÙ]´=#ÌnmåÃøúnæo—ÌQ‰÷ãÍ
‡É@.R0ðD©7Y4±íËퟆËS$³<7F>8

Двоичные данные
toolkit/components/downloads/test/unit/data/signed_win.exe Normal file

Двоичный файл не отображается.

Просмотреть файл

@ -62,72 +62,14 @@ function run_test() {
gHttpServ = new HttpServer();
gHttpServ.registerDirectory("/", do_get_cwd());
function createVerdict(aShouldBlock) {
// We can't programmatically create a protocol buffer here, so just
// hardcode some already serialized ones.
blob = String.fromCharCode(parseInt(0x08, 16));
if (aShouldBlock) {
// A safe_browsing::ClientDownloadRequest with a DANGEROUS verdict
blob += String.fromCharCode(parseInt(0x01, 16));
} else {
// A safe_browsing::ClientDownloadRequest with a SAFE verdict
blob += String.fromCharCode(parseInt(0x00, 16));
}
return blob;
}
gHttpServ.registerPathHandler("/download", function(request, response) {
response.setHeader("Content-Type", "application/octet-stream", false);
let buf = NetUtil.readInputStreamToString(
request.bodyInputStream,
request.bodyInputStream.available());
do_print("Request length: " + buf.length);
// A garbage response. By default this produces NS_CANNOT_CONVERT_DATA as
// the callback status.
let blob = "this is not a serialized protocol buffer";
// We can't actually parse the protocol buffer here, so just switch on the
// length instead of inspecting the contents.
if (buf.length == 35) {
// evil.com
blob = createVerdict(true);
} else if (buf.length == 38) {
// mozilla.com
blob = createVerdict(false);
}
response.bodyOutputStream.write(blob, blob.length);
do_throw("This test should never make a remote lookup");
});
gHttpServ.start(4444);
run_next_test();
}
/*
// Uncomment when remote lookups are enabled (bug 933432)
add_test(function test_shouldBlock() {
gAppRep.queryReputation({
sourceURI: createURI("http://evil.com"),
fileSize: 12,
}, function onComplete(aShouldBlock, aStatus) {
do_check_true(aShouldBlock);
do_check_eq(Cr.NS_OK, aStatus);
run_next_test();
});
});
add_test(function test_shouldNotBlock() {
gAppRep.queryReputation({
sourceURI: createURI("http://mozilla.com"),
fileSize: 12,
}, function onComplete(aShouldBlock, aStatus) {
do_check_eq(Cr.NS_OK, aStatus);
do_check_false(aShouldBlock);
run_next_test();
});
});
*/
add_test(function test_nullSourceURI() {
gAppRep.queryReputation({
// No source URI
@ -164,23 +106,6 @@ add_test(function test_disabled() {
});
});
/*
// Uncomment when remote lookups are enabled (bug 933432)
add_test(function test_garbage() {
Services.prefs.setCharPref("browser.safebrowsing.appRepURL",
"http://localhost:4444/download");
gAppRep.queryReputation({
sourceURI: createURI("http://whitelisted.com"),
fileSize: 12,
}, function onComplete(aShouldBlock, aStatus) {
// We should be getting the garbage response.
do_check_eq(Cr.NS_ERROR_CANNOT_CONVERT_DATA, aStatus);
do_check_false(aShouldBlock);
run_next_test();
});
});
*/
// Set up the local whitelist.
add_test(function test_local_list() {
// Construct a response with redirect urls.

Просмотреть файл

@ -0,0 +1,331 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* This file tests signature extraction using Windows Authenticode APIs of
* downloaded files.
*/
////////////////////////////////////////////////////////////////////////////////
//// Globals
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
"resource://gre/modules/FileUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
"resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/commonjs/sdk/core/promise.js");
XPCOMUtils.defineLazyModuleGetter(this, "Task",
"resource://gre/modules/Task.jsm");
const BackgroundFileSaverOutputStream = Components.Constructor(
"@mozilla.org/network/background-file-saver;1?mode=outputstream",
"nsIBackgroundFileSaver");
const StringInputStream = Components.Constructor(
"@mozilla.org/io/string-input-stream;1",
"nsIStringInputStream",
"setData");
const TEST_FILE_NAME_1 = "test-backgroundfilesaver-1.txt";
const gAppRep = Cc["@mozilla.org/downloads/application-reputation-service;1"].
getService(Ci.nsIApplicationReputationService);
let gStillRunning = true;
let gTables = {};
let gHttpServer = null;
/**
* Returns a reference to a temporary file. If the file is then created, it
* will be removed when tests in this file finish.
*/
function getTempFile(aLeafName) {
let file = FileUtils.getFile("TmpD", [aLeafName]);
do_register_cleanup(function GTF_cleanup() {
if (file.exists()) {
file.remove(false);
}
});
return file;
}
function readFileToString(aFilename) {
let f = do_get_file(aFilename);
let stream = Cc["@mozilla.org/network/file-input-stream;1"]
.createInstance(Ci.nsIFileInputStream);
stream.init(f, -1, 0, 0);
let buf = NetUtil.readInputStreamToString(stream, stream.available());
return buf;
}
/**
* Waits for the given saver object to complete.
*
* @param aSaver
* The saver, with the output stream or a stream listener implementation.
* @param aOnTargetChangeFn
* Optional callback invoked with the target file name when it changes.
*
* @return {Promise}
* @resolves When onSaveComplete is called with a success code.
* @rejects With an exception, if onSaveComplete is called with a failure code.
*/
function promiseSaverComplete(aSaver, aOnTargetChangeFn) {
let deferred = Promise.defer();
aSaver.observer = {
onTargetChange: function BFSO_onSaveComplete(aSaver, aTarget)
{
if (aOnTargetChangeFn) {
aOnTargetChangeFn(aTarget);
}
},
onSaveComplete: function BFSO_onSaveComplete(aSaver, aStatus)
{
if (Components.isSuccessCode(aStatus)) {
deferred.resolve();
} else {
deferred.reject(new Components.Exception("Saver failed.", aStatus));
}
},
};
return deferred.promise;
}
/**
* Feeds a string to a BackgroundFileSaverOutputStream.
*
* @param aSourceString
* The source data to copy.
* @param aSaverOutputStream
* The BackgroundFileSaverOutputStream to feed.
* @param aCloseWhenDone
* If true, the output stream will be closed when the copy finishes.
*
* @return {Promise}
* @resolves When the copy completes with a success code.
* @rejects With an exception, if the copy fails.
*/
function promiseCopyToSaver(aSourceString, aSaverOutputStream, aCloseWhenDone) {
let deferred = Promise.defer();
let inputStream = new StringInputStream(aSourceString, aSourceString.length);
let copier = Cc["@mozilla.org/network/async-stream-copier;1"]
.createInstance(Ci.nsIAsyncStreamCopier);
copier.init(inputStream, aSaverOutputStream, null, false, true, 0x8000, true,
aCloseWhenDone);
copier.asyncCopy({
onStartRequest: function () { },
onStopRequest: function (aRequest, aContext, aStatusCode)
{
if (Components.isSuccessCode(aStatusCode)) {
deferred.resolve();
} else {
deferred.reject(new Components.Exception(aResult));
}
},
}, null);
return deferred.promise;
}
// Registers a table for which to serve update chunks.
function registerTableUpdate(aTable, aFilename) {
// If we haven't been given an update for this table yet, add it to the map
if (!(aTable in gTables)) {
gTables[aTable] = [];
}
// The number of chunks associated with this table.
let numChunks = gTables[aTable].length + 1;
let redirectPath = "/" + aTable + "-" + numChunks;
let redirectUrl = "localhost:4444" + redirectPath;
// Store redirect url for that table so we can return it later when we
// process an update request.
gTables[aTable].push(redirectUrl);
gHttpServer.registerPathHandler(redirectPath, function(request, response) {
do_print("Mock safebrowsing server handling request for " + redirectPath);
let contents = readFileToString(aFilename);
do_print("Length of " + aFilename + ": " + contents.length);
response.setHeader("Content-Type",
"application/vnd.google.safebrowsing-update", false);
response.setStatusLine(request.httpVersion, 200, "OK");
response.bodyOutputStream.write(contents, contents.length);
});
}
////////////////////////////////////////////////////////////////////////////////
//// Tests
function run_test()
{
run_next_test();
}
add_task(function test_setup()
{
// Wait 10 minutes, that is half of the external xpcshell timeout.
do_timeout(10 * 60 * 1000, function() {
if (gStillRunning) {
do_throw("Test timed out.");
}
});
// Set up a local HTTP server to return bad verdicts.
Services.prefs.setCharPref("browser.safebrowsing.appRepURL",
"http://localhost:4444/download");
// Ensure safebrowsing is enabled for this test, even if the app
// doesn't have it enabled.
Services.prefs.setBoolPref("browser.safebrowsing.malware.enabled", true);
do_register_cleanup(function() {
Services.prefs.clearUserPref("browser.safebrowsing.malware.enabled");
});
gHttpServer = new HttpServer();
gHttpServer.registerDirectory("/", do_get_cwd());
function createVerdict(aShouldBlock) {
// We can't programmatically create a protocol buffer here, so just
// hardcode some already serialized ones.
blob = String.fromCharCode(parseInt(0x08, 16));
if (aShouldBlock) {
// A safe_browsing::ClientDownloadRequest with a DANGEROUS verdict
blob += String.fromCharCode(parseInt(0x01, 16));
} else {
// A safe_browsing::ClientDownloadRequest with a SAFE verdict
blob += String.fromCharCode(parseInt(0x00, 16));
}
return blob;
}
gHttpServer.registerPathHandler("/throw", function(request, response) {
do_throw("We shouldn't be getting here");
});
gHttpServer.registerPathHandler("/download", function(request, response) {
response.setHeader("Content-Type", "application/octet-stream", false);
let buf = NetUtil.readInputStreamToString(
request.bodyInputStream,
request.bodyInputStream.available());
do_print("Request length: " + buf.length);
// A garbage response. By default this produces NS_CANNOT_CONVERT_DATA as
// the callback status.
let blob = "this is not a serialized protocol buffer";
// We can't actually parse the protocol buffer here, so just switch on the
// length instead of inspecting the contents.
if (buf.length == 35) {
// evil.com
blob = createVerdict(true);
} else if (buf.length == 38) {
// mozilla.com
blob = createVerdict(false);
}
response.bodyOutputStream.write(blob, blob.length);
});
gHttpServer.start(4444);
});
// Construct a response with redirect urls.
function processUpdateRequest() {
let response = "n:1000\n";
for (let table in gTables) {
response += "i:" + table + "\n";
for (let i = 0; i < gTables[table].length; ++i) {
response += "u:" + gTables[table][i] + "\n";
}
}
do_print("Returning update response: " + response);
return response;
}
// Set up the local whitelist.
function waitForUpdates() {
let deferred = Promise.defer();
gHttpServer.registerPathHandler("/downloads", function(request, response) {
let buf = NetUtil.readInputStreamToString(request.bodyInputStream,
request.bodyInputStream.available());
let blob = processUpdateRequest();
response.setHeader("Content-Type",
"application/vnd.google.safebrowsing-update", false);
response.setStatusLine(request.httpVersion, 200, "OK");
response.bodyOutputStream.write(blob, blob.length);
});
let streamUpdater = Cc["@mozilla.org/url-classifier/streamupdater;1"]
.getService(Ci.nsIUrlClassifierStreamUpdater);
streamUpdater.updateUrl = "http://localhost:4444/downloads";
// Load up some update chunks for the safebrowsing server to serve. This
// particular chunk contains the hash of whitelisted.com/ and
// sb-ssl.google.com/safebrowsing/csd/certificate/.
registerTableUpdate("goog-downloadwhite-digest256", "data/digest.chunk");
// Resolve the promise once processing the updates is complete.
function updateSuccess(aEvent) {
// Timeout of n:1000 is constructed in processUpdateRequest above and
// passed back in the callback in nsIUrlClassifierStreamUpdater on success.
do_check_eq("1000", aEvent);
do_print("All data processed");
deferred.resolve(true);
}
// Just throw if we ever get an update or download error.
function handleError(aEvent) {
do_throw("We didn't download or update correctly: " + aEvent);
deferred.reject();
}
streamUpdater.downloadUpdates(
"goog-downloadwhite-digest256",
"goog-downloadwhite-digest256;\n",
updateSuccess, handleError, handleError);
return deferred.promise;
}
function promiseQueryReputation(query, expectedShouldBlock) {
let deferred = Promise.defer();
function onComplete(aShouldBlock, aStatus) {
do_check_eq(Cr.NS_OK, aStatus);
do_check_eq(aShouldBlock, expectedShouldBlock);
deferred.resolve(true);
}
gAppRep.queryReputation(query, onComplete);
return deferred.promise;
}
add_task(function test_signature_whitelists()
{
// We should never get to the remote server.
Services.prefs.setCharPref("browser.safebrowsing.appRepURL",
"http://localhost:4444/throw");
// Wait for Safebrowsing local list updates to complete.
yield waitForUpdates();
// Use BackgroundFileSaver to extract the signature on Windows.
let destFile = getTempFile(TEST_FILE_NAME_1);
let data = readFileToString("data/signed_win.exe");
let saver = new BackgroundFileSaverOutputStream();
let completionPromise = promiseSaverComplete(saver);
saver.enableSignatureInfo();
saver.setTarget(destFile, false);
yield promiseCopyToSaver(data, saver, true);
saver.finish(Cr.NS_OK);
yield completionPromise;
// Clean up.
destFile.remove(false);
// evil.com is not on the allowlist, but this binary is signed by an entity
// whose certificate information is on the allowlist.
yield promiseQueryReputation({sourceURI: createURI("http://evil.com"),
signatureInfo: saver.signatureInfo,
fileSize: 12}, false);
});
add_task(function test_teardown()
{
gStillRunning = false;
});

Просмотреть файл

@ -6,8 +6,11 @@ support-files =
downloads_manifest.js
test_downloads.manifest
data/digest.chunk
data/signed_win.exe
[test_app_rep.js]
[test_app_rep_windows.js]
run-if = os == "win"
[test_bug_382825.js]
[test_bug_384744.js]
[test_bug_395092.js]