mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 964465: Check certificate whitelist strings before sending remote lookup (r=gcp)
This commit is contained in:
parent
05b1319dbf
commit
b0fc37a363
@ -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
|
BIN
toolkit/components/downloads/test/unit/data/signed_win.exe
Normal file
BIN
toolkit/components/downloads/test/unit/data/signed_win.exe
Normal file
Binary file not shown.
@ -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.
|
||||
|
331
toolkit/components/downloads/test/unit/test_app_rep_windows.js
Normal file
331
toolkit/components/downloads/test/unit/test_app_rep_windows.js
Normal file
@ -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]
|
||||
|
Loading…
Reference in New Issue
Block a user