mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-09 19:35:51 +00:00
Bug 928536: Use WinVerifyTrust to get certificate information on downloaded binaries (r=paolo,keeler,sr=mossop)
This commit is contained in:
parent
c15e175b05
commit
55632d760b
@ -6,6 +6,7 @@
|
||||
|
||||
#include "nsISupports.idl"
|
||||
|
||||
interface nsIArray;
|
||||
interface nsIBackgroundFileSaverObserver;
|
||||
interface nsIFile;
|
||||
|
||||
@ -38,7 +39,7 @@ interface nsIFile;
|
||||
* public methods of the interface may only be called from the main
|
||||
* thread.
|
||||
*/
|
||||
[scriptable, uuid(581a99ca-dc8d-4cee-ac95-99156e7517ed)]
|
||||
[scriptable, uuid(c43544a4-682c-4262-b407-2453d26e660d)]
|
||||
interface nsIBackgroundFileSaver : nsISupports
|
||||
{
|
||||
/**
|
||||
@ -52,13 +53,16 @@ interface nsIBackgroundFileSaver : nsISupports
|
||||
attribute nsIBackgroundFileSaverObserver observer;
|
||||
|
||||
/**
|
||||
* Instructs the component to compute the SHA-256 hash of the target file, and
|
||||
* make it available in the sha256Hash property.
|
||||
* An nsIArray of nsIX509CertList, representing a chain of X.509 signatures on
|
||||
* the downloaded file. Each list may belong to a different signer and contain
|
||||
* certificates all the way up to the root.
|
||||
*
|
||||
* @remarks This must be set on the main thread before the first call to
|
||||
* setTarget.
|
||||
* @throws NS_ERROR_NOT_AVAILABLE
|
||||
* In case this is called before the onSaveComplete method has been
|
||||
* called to notify success, or enableSignatureInfo has not been
|
||||
* called.
|
||||
*/
|
||||
void enableSha256();
|
||||
readonly attribute nsIArray signatureInfo;
|
||||
|
||||
/**
|
||||
* The SHA-256 hash, in raw bytes, associated with the data that was saved.
|
||||
@ -72,6 +76,24 @@ interface nsIBackgroundFileSaver : nsISupports
|
||||
*/
|
||||
readonly attribute ACString sha256Hash;
|
||||
|
||||
/**
|
||||
* Instructs the component to compute the signatureInfo of the target file,
|
||||
* and make it available in the signatureInfo property.
|
||||
*
|
||||
* @remarks This must be set on the main thread before the first call to
|
||||
* setTarget.
|
||||
*/
|
||||
void enableSignatureInfo();
|
||||
|
||||
/**
|
||||
* Instructs the component to compute the SHA-256 hash of the target file, and
|
||||
* make it available in the sha256Hash property.
|
||||
*
|
||||
* @remarks This must be set on the main thread before the first call to
|
||||
* setTarget.
|
||||
*/
|
||||
void enableSha256();
|
||||
|
||||
/**
|
||||
* Instructs the component to append data to the initial target file, that
|
||||
* will be specified by the first call to the setTarget method, instead of
|
||||
|
@ -5,21 +5,45 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "pk11pub.h"
|
||||
#include "prlog.h"
|
||||
#include "ScopedNSSTypes.h"
|
||||
#include "secoidt.h"
|
||||
|
||||
#include "nsIAsyncInputStream.h"
|
||||
#include "nsIFile.h"
|
||||
#include "nsIMutableArray.h"
|
||||
#include "nsIPipe.h"
|
||||
#include "nsIX509Cert.h"
|
||||
#include "nsIX509CertDB.h"
|
||||
#include "nsIX509CertList.h"
|
||||
#include "nsCOMArray.h"
|
||||
#include "nsNetUtil.h"
|
||||
#include "nsThreadUtils.h"
|
||||
|
||||
#include "BackgroundFileSaver.h"
|
||||
#include "mozilla/Telemetry.h"
|
||||
#include "nsIAsyncInputStream.h"
|
||||
|
||||
#ifdef XP_WIN
|
||||
#include <windows.h>
|
||||
#include <softpub.h>
|
||||
#include <wintrust.h>
|
||||
|
||||
#pragma comment(lib, "wintrust.lib")
|
||||
#endif // XP_WIN
|
||||
|
||||
namespace mozilla {
|
||||
namespace net {
|
||||
|
||||
// NSPR_LOG_MODULES=BackgroundFileSaver:5
|
||||
#if defined(PR_LOGGING)
|
||||
PRLogModuleInfo *BackgroundFileSaver::prlog = nullptr;
|
||||
#define LOG(args) PR_LOG(BackgroundFileSaver::prlog, PR_LOG_DEBUG, args)
|
||||
#define LOG_ENABLED() PR_LOG_TEST(BackgroundFileSaver::prlog, 4)
|
||||
#else
|
||||
#define LOG(args)
|
||||
#define LOG_ENABLED() (false)
|
||||
#endif
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// Globals
|
||||
|
||||
@ -88,14 +112,21 @@ BackgroundFileSaver::BackgroundFileSaver()
|
||||
, mRenamedTargetKeepPartial(false)
|
||||
, mAsyncCopyContext(nullptr)
|
||||
, mSha256Enabled(false)
|
||||
, mSignatureInfoEnabled(false)
|
||||
, mActualTarget(nullptr)
|
||||
, mActualTargetKeepPartial(false)
|
||||
, mDigestContext(nullptr)
|
||||
{
|
||||
#if defined(PR_LOGGING)
|
||||
if (!prlog)
|
||||
prlog = PR_NewLogModule("BackgroundFileSaver");
|
||||
#endif
|
||||
LOG(("Created BackgroundFileSaver [this = %p]", this));
|
||||
}
|
||||
|
||||
BackgroundFileSaver::~BackgroundFileSaver()
|
||||
{
|
||||
LOG(("Destroying BackgroundFileSaver [this = %p]", this));
|
||||
nsNSSShutDownPreventionLock lock;
|
||||
if (isAlreadyShutDown()) {
|
||||
return;
|
||||
@ -231,12 +262,12 @@ BackgroundFileSaver::EnableSha256()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread(),
|
||||
"Can't enable sha256 or initialize NSS off the main thread");
|
||||
mSha256Enabled = true;
|
||||
// Ensure Personal Security Manager is initialized. This is required for
|
||||
// PK11_* operations to work.
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsISupports> nssDummy = do_GetService("@mozilla.org/psm;1", &rv);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
mSha256Enabled = true;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
@ -253,6 +284,37 @@ BackgroundFileSaver::GetSha256Hash(nsACString& aHash)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
BackgroundFileSaver::EnableSignatureInfo()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread(),
|
||||
"Can't enable signature extraction off the main thread");
|
||||
// Ensure Personal Security Manager is initialized.
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsISupports> nssDummy = do_GetService("@mozilla.org/psm;1", &rv);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
mSignatureInfoEnabled = true;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
BackgroundFileSaver::GetSignatureInfo(nsIArray** aSignatureInfo)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread(), "Can't inspect signature off the main thread");
|
||||
// We acquire a lock because mSignatureInfo is written on the worker thread.
|
||||
MutexAutoLock lock(mLock);
|
||||
if (!mComplete || !mSignatureInfoEnabled) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
nsCOMPtr<nsIMutableArray> sigArray = do_CreateInstance(NS_ARRAY_CONTRACTID);
|
||||
for (int i = 0; i < mSignatureInfo.Count(); ++i) {
|
||||
sigArray->AppendElement(mSignatureInfo[i], false);
|
||||
}
|
||||
*aSignatureInfo = sigArray;
|
||||
NS_IF_ADDREF(*aSignatureInfo);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Called on the control thread.
|
||||
nsresult
|
||||
BackgroundFileSaver::GetWorkerThreadAttention(bool aShouldInterruptCopy)
|
||||
@ -679,6 +741,19 @@ BackgroundFileSaver::CheckCompletion()
|
||||
}
|
||||
}
|
||||
|
||||
// Compute the signature of the binary. ExtractSignatureInfo doesn't do
|
||||
// anything on non-Windows platforms except return an empty nsIArray.
|
||||
if (!failed && mActualTarget) {
|
||||
nsString filePath;
|
||||
mActualTarget->GetTarget(filePath);
|
||||
nsresult rv = ExtractSignatureInfo(filePath);
|
||||
if (NS_FAILED(rv)) {
|
||||
LOG(("Unable to extract signature information [this = %p].", this));
|
||||
} else {
|
||||
LOG(("Signature extraction success! [this = %p]", this));
|
||||
}
|
||||
}
|
||||
|
||||
// Post an event to notify that the operation completed.
|
||||
nsCOMPtr<nsIRunnable> event =
|
||||
NS_NewRunnableMethod(this, &BackgroundFileSaver::NotifySaveComplete);
|
||||
@ -741,6 +816,124 @@ BackgroundFileSaver::NotifySaveComplete()
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
BackgroundFileSaver::ExtractSignatureInfo(const nsAString& filePath)
|
||||
{
|
||||
MOZ_ASSERT(!NS_IsMainThread(), "Cannot extract signature on main thread");
|
||||
|
||||
nsNSSShutDownPreventionLock nssLock;
|
||||
if (isAlreadyShutDown()) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
{
|
||||
MutexAutoLock lock(mLock);
|
||||
if (!mSignatureInfoEnabled) {
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsIX509CertDB> certDB = do_GetService(NS_X509CERTDB_CONTRACTID, &rv);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
#ifdef XP_WIN
|
||||
// Setup the file to check.
|
||||
WINTRUST_FILE_INFO fileToCheck = {0};
|
||||
fileToCheck.cbStruct = sizeof(WINTRUST_FILE_INFO);
|
||||
fileToCheck.pcwszFilePath = filePath.Data();
|
||||
fileToCheck.hFile = nullptr;
|
||||
fileToCheck.pgKnownSubject = nullptr;
|
||||
|
||||
// We want to check it is signed and trusted.
|
||||
WINTRUST_DATA trustData = {0};
|
||||
trustData.cbStruct = sizeof(trustData);
|
||||
trustData.pPolicyCallbackData = nullptr;
|
||||
trustData.pSIPClientData = nullptr;
|
||||
trustData.dwUIChoice = WTD_UI_NONE;
|
||||
trustData.fdwRevocationChecks = WTD_REVOKE_NONE;
|
||||
trustData.dwUnionChoice = WTD_CHOICE_FILE;
|
||||
trustData.dwStateAction = WTD_STATEACTION_VERIFY;
|
||||
trustData.hWVTStateData = nullptr;
|
||||
trustData.pwszURLReference = nullptr;
|
||||
// Disallow revocation checks over the network
|
||||
trustData.dwProvFlags = WTD_CACHE_ONLY_URL_RETRIEVAL;
|
||||
// no UI
|
||||
trustData.dwUIContext = 0;
|
||||
trustData.pFile = &fileToCheck;
|
||||
|
||||
// The WINTRUST_ACTION_GENERIC_VERIFY_V2 policy verifies that the certificate
|
||||
// chains up to a trusted root CA and has appropriate permissions to sign
|
||||
// code.
|
||||
GUID policyGUID = WINTRUST_ACTION_GENERIC_VERIFY_V2;
|
||||
// Check if the file is signed by something that is trusted. If the file is
|
||||
// not signed, this is a no-op.
|
||||
LONG ret = WinVerifyTrust(nullptr, &policyGUID, &trustData);
|
||||
CRYPT_PROVIDER_DATA* cryptoProviderData = nullptr;
|
||||
// According to the Windows documentation, we should check against 0 instead
|
||||
// of ERROR_SUCCESS, which is an HRESULT.
|
||||
if (ret == 0) {
|
||||
cryptoProviderData = WTHelperProvDataFromStateData(trustData.hWVTStateData);
|
||||
}
|
||||
if (cryptoProviderData) {
|
||||
// Lock because signature information is read on the main thread.
|
||||
MutexAutoLock lock(mLock);
|
||||
LOG(("Downloaded trusted and signed file [this = %p].", this));
|
||||
// A binary may have multiple signers. Each signer may have multiple certs
|
||||
// in the chain.
|
||||
for (DWORD i = 0; i < cryptoProviderData->csSigners; ++i) {
|
||||
const CERT_CHAIN_CONTEXT* certChainContext =
|
||||
cryptoProviderData->pasSigners[i].pChainContext;
|
||||
if (!certChainContext) {
|
||||
break;
|
||||
}
|
||||
for (DWORD j = 0; j < certChainContext->cChain; ++j) {
|
||||
const CERT_SIMPLE_CHAIN* certSimpleChain =
|
||||
certChainContext->rgpChain[j];
|
||||
if (!certSimpleChain) {
|
||||
break;
|
||||
}
|
||||
nsCOMPtr<nsIX509CertList> nssCertList =
|
||||
do_CreateInstance(NS_X509CERTLIST_CONTRACTID);
|
||||
if (!nssCertList) {
|
||||
break;
|
||||
}
|
||||
bool extractionSuccess = true;
|
||||
for (DWORD k = 0; k < certSimpleChain->cElement; ++k) {
|
||||
CERT_CHAIN_ELEMENT* certChainElement = certSimpleChain->rgpElement[k];
|
||||
if (certChainElement->pCertContext->dwCertEncodingType !=
|
||||
X509_ASN_ENCODING) {
|
||||
continue;
|
||||
}
|
||||
nsCOMPtr<nsIX509Cert> nssCert = nullptr;
|
||||
rv = certDB->ConstructX509(
|
||||
reinterpret_cast<char *>(
|
||||
certChainElement->pCertContext->pbCertEncoded),
|
||||
certChainElement->pCertContext->cbCertEncoded,
|
||||
getter_AddRefs(nssCert));
|
||||
if (!nssCert) {
|
||||
extractionSuccess = false;
|
||||
LOG(("Couldn't create NSS cert [this = %p]", this));
|
||||
break;
|
||||
}
|
||||
nssCertList->AddCert(nssCert);
|
||||
nsString subjectName;
|
||||
nssCert->GetSubjectName(subjectName);
|
||||
LOG(("Adding cert %s [this = %p]",
|
||||
NS_ConvertUTF16toUTF8(subjectName).get(), this));
|
||||
}
|
||||
if (extractionSuccess) {
|
||||
mSignatureInfo.AppendObject(nssCertList);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Free the provider data if cryptoProviderData is not null.
|
||||
trustData.dwStateAction = WTD_STATEACTION_CLOSE;
|
||||
WinVerifyTrust(nullptr, &policyGUID, &trustData);
|
||||
} else {
|
||||
LOG(("Downloaded unsigned or untrusted file [this = %p].", this));
|
||||
}
|
||||
#endif
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// BackgroundFileSaverOutputStream
|
||||
|
||||
@ -1079,6 +1272,5 @@ DigestOutputStream::IsNonBlocking(bool *retval)
|
||||
return mOutputStream->IsNonBlocking(retval);
|
||||
}
|
||||
|
||||
|
||||
} // namespace net
|
||||
} // namespace mozilla
|
||||
|
@ -13,6 +13,7 @@
|
||||
#define BackgroundFileSaver_h__
|
||||
|
||||
#include "mozilla/Mutex.h"
|
||||
#include "nsCOMArray.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsNSSShutDown.h"
|
||||
#include "nsIAsyncOutputStream.h"
|
||||
@ -23,6 +24,8 @@
|
||||
|
||||
class nsIAsyncInputStream;
|
||||
class nsIThread;
|
||||
class nsIX509CertList;
|
||||
class PRLogModuleInfo;
|
||||
|
||||
namespace mozilla {
|
||||
namespace net {
|
||||
@ -71,6 +74,8 @@ public:
|
||||
protected:
|
||||
virtual ~BackgroundFileSaver();
|
||||
|
||||
static PRLogModuleInfo *prlog;
|
||||
|
||||
/**
|
||||
* Helper function for managing NSS objects (mDigestContext).
|
||||
*/
|
||||
@ -211,6 +216,17 @@ private:
|
||||
*/
|
||||
bool mSha256Enabled;
|
||||
|
||||
/**
|
||||
* Store the signature info.
|
||||
*/
|
||||
nsCOMArray<nsIX509CertList> mSignatureInfo;
|
||||
|
||||
/**
|
||||
* Whether or not to extract the signature. Must be set on the main thread
|
||||
* before setTarget is called.
|
||||
*/
|
||||
bool mSignatureInfoEnabled;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//// State handled exclusively by the worker thread
|
||||
|
||||
@ -281,6 +297,13 @@ private:
|
||||
* Event called on the control thread to send the final notification.
|
||||
*/
|
||||
nsresult NotifySaveComplete();
|
||||
|
||||
/**
|
||||
* Verifies the signature of the binary at the specified file path and stores
|
||||
* the signature data in mSignatureInfo. We extract only X.509 certificates,
|
||||
* since that is what Google's Safebrowsing protocol specifies.
|
||||
*/
|
||||
nsresult ExtractSignatureInfo(const nsAString& filePath);
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
BIN
netwerk/test/unit/data/signed_win.exe
Normal file
BIN
netwerk/test/unit/data/signed_win.exe
Normal file
Binary file not shown.
@ -295,6 +295,14 @@ add_task(function test_combinations()
|
||||
let initialFile = getTempFile(TEST_FILE_NAME_1);
|
||||
let renamedFile = getTempFile(TEST_FILE_NAME_2);
|
||||
|
||||
// Keep track of the current file.
|
||||
let currentFile = null;
|
||||
function onTargetChange(aTarget) {
|
||||
currentFile = null;
|
||||
do_print("Target file changed to: " + aTarget.leafName);
|
||||
currentFile = aTarget;
|
||||
}
|
||||
|
||||
// Tests various combinations of events and behaviors for both the stream
|
||||
// listener and the output stream implementations.
|
||||
for (let testFlags = 0; testFlags < 32; testFlags++) {
|
||||
@ -311,14 +319,8 @@ add_task(function test_combinations()
|
||||
", useStreamListener = " + useStreamListener +
|
||||
", useLongData = " + useLongData);
|
||||
|
||||
// Keep track of the current file.
|
||||
let currentFile = null;
|
||||
function onTargetChange(aTarget) {
|
||||
do_print("Target file changed to: " + aTarget.leafName);
|
||||
currentFile = aTarget;
|
||||
}
|
||||
|
||||
// Create the object and register the observers.
|
||||
currentFile = null;
|
||||
let saver = useStreamListener
|
||||
? new BackgroundFileSaverStreamListener()
|
||||
: new BackgroundFileSaverOutputStream();
|
||||
@ -365,7 +367,7 @@ add_task(function test_combinations()
|
||||
if (!cancelAtSomePoint) {
|
||||
// In this case, the file must exist.
|
||||
do_check_true(currentFile.exists());
|
||||
expectedContents = testData + testData;
|
||||
let expectedContents = testData + testData;
|
||||
yield promiseVerifyContents(currentFile, expectedContents);
|
||||
do_check_eq(EXPECTED_HASHES[expectedContents.length],
|
||||
toHex(saver.sha256Hash));
|
||||
@ -668,6 +670,55 @@ add_task(function test_invalid_hash()
|
||||
} catch (ex if ex.result == Cr.NS_ERROR_FAILURE) { }
|
||||
});
|
||||
|
||||
add_task(function test_signature()
|
||||
{
|
||||
// Check that we get a signature if the saver is finished.
|
||||
let destFile = getTempFile(TEST_FILE_NAME_1);
|
||||
|
||||
let saver = new BackgroundFileSaverOutputStream();
|
||||
let completionPromise = promiseSaverComplete(saver);
|
||||
|
||||
try {
|
||||
let signatureInfo = saver.signatureInfo;
|
||||
do_throw("Can't get signature if saver is not complete");
|
||||
} catch (ex if ex.result == Cr.NS_ERROR_NOT_AVAILABLE) { }
|
||||
|
||||
saver.enableSignatureInfo();
|
||||
saver.setTarget(destFile, false);
|
||||
yield promiseCopyToSaver(TEST_DATA_SHORT, saver, true);
|
||||
|
||||
saver.finish(Cr.NS_OK);
|
||||
yield completionPromise;
|
||||
yield promiseVerifyContents(destFile, TEST_DATA_SHORT);
|
||||
|
||||
// signatureInfo is an empty nsIArray
|
||||
do_check_eq(0, saver.signatureInfo.length);
|
||||
|
||||
// Clean up.
|
||||
destFile.remove(false);
|
||||
});
|
||||
|
||||
add_task(function test_signature_not_enabled()
|
||||
{
|
||||
// Check that we get a signature if the saver is finished on Windows.
|
||||
let destFile = getTempFile(TEST_FILE_NAME_1);
|
||||
|
||||
let saver = new BackgroundFileSaverOutputStream();
|
||||
let completionPromise = promiseSaverComplete(saver);
|
||||
saver.setTarget(destFile, false);
|
||||
yield promiseCopyToSaver(TEST_DATA_SHORT, saver, true);
|
||||
|
||||
saver.finish(Cr.NS_OK);
|
||||
yield completionPromise;
|
||||
try {
|
||||
let signatureInfo = saver.signatureInfo;
|
||||
do_throw("Can't get signature if not enabled");
|
||||
} catch (ex if ex.result == Cr.NS_ERROR_NOT_AVAILABLE) { }
|
||||
|
||||
// Clean up.
|
||||
destFile.remove(false);
|
||||
});
|
||||
|
||||
add_task(function test_teardown()
|
||||
{
|
||||
gStillRunning = false;
|
||||
|
206
netwerk/test/unit/test_signature_extraction.js
Normal file
206
netwerk/test/unit/test_signature_extraction.js
Normal file
@ -0,0 +1,206 @@
|
||||
/* -*- 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";
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
let gStillRunning = true;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// 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.");
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
add_task(function test_signature()
|
||||
{
|
||||
// Check that we get a signature if the saver is finished on Windows.
|
||||
let destFile = getTempFile(TEST_FILE_NAME_1);
|
||||
|
||||
let data = readFileToString("data/signed_win.exe");
|
||||
let saver = new BackgroundFileSaverOutputStream();
|
||||
let completionPromise = promiseSaverComplete(saver);
|
||||
|
||||
try {
|
||||
let signatureInfo = saver.signatureInfo;
|
||||
do_throw("Can't get signature before saver is complete.");
|
||||
} catch (ex if ex.result == Cr.NS_ERROR_NOT_AVAILABLE) { }
|
||||
|
||||
saver.enableSignatureInfo();
|
||||
saver.setTarget(destFile, false);
|
||||
yield promiseCopyToSaver(data, saver, true);
|
||||
|
||||
saver.finish(Cr.NS_OK);
|
||||
yield completionPromise;
|
||||
|
||||
// There's only one nsIX509CertList in the signature array.
|
||||
do_check_eq(1, saver.signatureInfo.length);
|
||||
let certLists = saver.signatureInfo.enumerate();
|
||||
do_check_true(certLists.hasMoreElements());
|
||||
let certList = certLists.getNext().QueryInterface(Ci.nsIX509CertList);
|
||||
do_check_false(certLists.hasMoreElements());
|
||||
|
||||
// Check that it has 3 certs.
|
||||
let certs = certList.getEnumerator();
|
||||
do_check_true(certs.hasMoreElements());
|
||||
let signer = certs.getNext().QueryInterface(Ci.nsIX509Cert);
|
||||
do_check_true(certs.hasMoreElements());
|
||||
let issuer = certs.getNext().QueryInterface(Ci.nsIX509Cert);
|
||||
do_check_true(certs.hasMoreElements());
|
||||
let root = certs.getNext().QueryInterface(Ci.nsIX509Cert);
|
||||
do_check_false(certs.hasMoreElements());
|
||||
|
||||
// Check that the certs have expected strings attached.
|
||||
let organization = "Microsoft Corporation";
|
||||
do_check_eq("Microsoft Corporation", signer.commonName);
|
||||
do_check_eq(organization, signer.organization);
|
||||
do_check_eq("Copyright (c) 2002 Microsoft Corp.", signer.organizationalUnit);
|
||||
|
||||
do_check_eq("Microsoft Code Signing PCA", issuer.commonName);
|
||||
do_check_eq(organization, issuer.organization);
|
||||
do_check_eq("Copyright (c) 2000 Microsoft Corp.", issuer.organizationalUnit);
|
||||
|
||||
do_check_eq("Microsoft Root Authority", root.commonName);
|
||||
do_check_false(root.organization);
|
||||
do_check_eq("Copyright (c) 1997 Microsoft Corp.", root.organizationalUnit);
|
||||
|
||||
// Clean up.
|
||||
destFile.remove(false);
|
||||
});
|
||||
|
||||
add_task(function test_teardown()
|
||||
{
|
||||
gStillRunning = false;
|
||||
});
|
@ -13,6 +13,7 @@ support-files =
|
||||
data/test_readline6.txt
|
||||
data/test_readline7.txt
|
||||
data/test_readline8.txt
|
||||
data/signed_win.exe
|
||||
socks_client_subprocess.js
|
||||
test_link.desktop
|
||||
test_link.url
|
||||
@ -309,3 +310,5 @@ skip-if = os == "android"
|
||||
# disable this test on all android versions, even though it's enabled on 2.3+ in
|
||||
# the wild.
|
||||
skip-if = os == "android"
|
||||
[test_signature_extraction.js]
|
||||
run-if = os == "win"
|
||||
|
Loading…
Reference in New Issue
Block a user