mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-25 13:51:41 +00:00
Bug 1178525 - Introduce PackagedAppVerifier and use it to control the timing we serve packaged content. r=valentin.
This commit is contained in:
parent
34122d2ae8
commit
d0d222b8de
@ -1435,6 +1435,11 @@ pref("network.http.enforce-framing.soft", true);
|
||||
// See http://www.w3.org/TR/web-packaging/#streamable-package-format
|
||||
pref("network.http.enable-packaged-apps", false);
|
||||
|
||||
// Enable this pref to skip verification process. The packaged app
|
||||
// will be considered signed no matter the package has a valid/invalid
|
||||
// signature or no signature.
|
||||
pref("network.http.packaged-apps-developer-mode", false);
|
||||
|
||||
// default values for FTP
|
||||
// in a DSCP environment this should be 40 (0x28, or AF11), per RFC-4594,
|
||||
// Section 4.8 "High-Throughput Data Service Class", and 80 (0x50, or AF22)
|
||||
|
@ -69,6 +69,7 @@ XPIDL_SOURCES += [
|
||||
'nsINullChannel.idl',
|
||||
'nsIPACGenerator.idl',
|
||||
'nsIPackagedAppService.idl',
|
||||
'nsIPackagedAppVerifier.idl',
|
||||
'nsIParentChannel.idl',
|
||||
'nsIParentRedirectingChannel.idl',
|
||||
'nsIPermission.idl',
|
||||
|
115
netwerk/base/nsIPackagedAppVerifier.idl
Normal file
115
netwerk/base/nsIPackagedAppVerifier.idl
Normal file
@ -0,0 +1,115 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* 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/. */
|
||||
|
||||
#include "nsISupports.idl"
|
||||
#include "nsIStreamListener.idl"
|
||||
|
||||
interface nsIURI;
|
||||
interface nsICacheEntry;
|
||||
interface nsIPackagedAppVerifierListener;
|
||||
|
||||
/**
|
||||
* nsIPackagedAppVerifier
|
||||
*
|
||||
* It inherits nsIStreamListener and all the data will be fed by
|
||||
* onStartRequest/onDataAvailable/onStopRequest.
|
||||
*
|
||||
*/
|
||||
[scriptable, uuid(16419a80-4cc3-11e5-b970-0800200c9a66)]
|
||||
interface nsIPackagedAppVerifier : nsIStreamListener
|
||||
{
|
||||
// The package origin of either a signed or unsigned package.
|
||||
readonly attribute ACString packageOrigin;
|
||||
|
||||
// Whether this package is signed.
|
||||
readonly attribute boolean isPackageSigned;
|
||||
|
||||
/**
|
||||
* @param aListener
|
||||
* an object implementing nsIPackagedAppVerifierListener as the bridge that
|
||||
* the client gets callback from the package verifier. The callback might be
|
||||
* sync or async depending on the implementation.
|
||||
*
|
||||
* @param aPackageOrigin
|
||||
* the origin of the package. It will be updated based on the package
|
||||
* identifier defined in the manifest.
|
||||
*
|
||||
* @param aSignature
|
||||
* the signature of the package we desire to verify against. See
|
||||
* https://wiki.mozilla.org/User:Ptheriault/Packagedprivilegedcontent#The_Signed_Manifest
|
||||
* for further information.
|
||||
*
|
||||
* @param aPackageCacheEntry
|
||||
* the cache entry of the package itself (not the resource's cache).
|
||||
* It will be used to store any necessary information like the signed
|
||||
* package origin.
|
||||
*
|
||||
* The verifier init function.
|
||||
*/
|
||||
void init(in nsIPackagedAppVerifierListener aListener,
|
||||
in ACString aPackageOrigin,
|
||||
in ACString aSignature,
|
||||
in nsICacheEntry aPackageCacheEntry);
|
||||
|
||||
/**
|
||||
* @param aUri
|
||||
* the URI of the resource.
|
||||
*
|
||||
* @param aCacheEntry
|
||||
* the cache entry of the resource.
|
||||
*
|
||||
* @param aStatusCode
|
||||
* the status code of the resource we just finished download.
|
||||
*
|
||||
* @param aIsLastPart
|
||||
* whether this resource is the last one in the package.
|
||||
*
|
||||
* Create an object that we will pass to the verifier as a user context
|
||||
* through onStartRequest. The main purpose of this function is to make
|
||||
* nsIPackagedAppVerifier xpcshell-testable. See test_packaged_app_verifier.js.
|
||||
*
|
||||
*/
|
||||
nsISupports createResourceCacheInfo(in nsIURI aUri,
|
||||
in nsICacheEntry aCacheEntry,
|
||||
in nsresult aStatusCode,
|
||||
in boolean aIsLastPart);
|
||||
};
|
||||
|
||||
/**
|
||||
* nsIPackagedAppVerifierListener
|
||||
*/
|
||||
[scriptable, uuid(092eba70-4cbf-11e5-b970-0800200c9a66)]
|
||||
interface nsIPackagedAppVerifierListener : nsISupports
|
||||
{
|
||||
/**
|
||||
* @param aIsManifest
|
||||
* indicate if this callback is for manifest or not. True for manifest and false
|
||||
* for resource.
|
||||
*
|
||||
* @param aUri
|
||||
* the URI of the resource that has just been verified.
|
||||
*
|
||||
* @param aCacheEntry
|
||||
* the cache entry of the resource that has just been verified.
|
||||
*
|
||||
* @param aStatusCode
|
||||
* the resource download status code from nsIMultipartChannel.
|
||||
*
|
||||
* @param aIsLastPart
|
||||
* indicate if the verified resource is that last one in the package.
|
||||
*
|
||||
* @param aVerificationSuccess
|
||||
* the verification result.
|
||||
*
|
||||
* Callback'ed when a manifest/resource is verified.
|
||||
*/
|
||||
void onVerified(in boolean aIsManifest,
|
||||
in nsIURI aUri,
|
||||
in nsICacheEntry aCacheEntry,
|
||||
in nsresult aStatusCode,
|
||||
in boolean aIsLastPart,
|
||||
in boolean aVerificationSuccess);
|
||||
};
|
||||
|
@ -906,6 +906,15 @@
|
||||
{ 0xa0, 0x6a, 0xdc, 0x29, 0xcf, 0x8d, 0xe3, 0x81 } \
|
||||
}
|
||||
|
||||
#define NS_PACKAGEDAPPVERIFIER_CONTRACTID \
|
||||
"@mozilla.org/network/packaged-app-verifier;1"
|
||||
#define NS_PACKAGEDAPPVERIFIER_CID \
|
||||
{ /* 07242d20-4cae-11e5-b970-0800200c9a66 */ \
|
||||
0x07242d20, \
|
||||
0x4cae, \
|
||||
0x11e5, \
|
||||
{ 0xb9, 0x70, 0x08, 0x00, 0x20, 0x0c, 0x96, 0x66 } \
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* netwerk/cookie classes
|
||||
|
@ -264,10 +264,12 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsHttpDigestAuth)
|
||||
|
||||
#include "mozilla/net/Dashboard.h"
|
||||
#include "mozilla/net/PackagedAppService.h"
|
||||
#include "mozilla/net/PackagedAppVerifier.h"
|
||||
namespace mozilla {
|
||||
namespace net {
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR(Dashboard)
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR(PackagedAppService)
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR(PackagedAppVerifier)
|
||||
} // namespace net
|
||||
} // namespace mozilla
|
||||
#include "AppProtocolHandler.h"
|
||||
@ -732,6 +734,7 @@ NS_DEFINE_NAMED_CID(NS_MIMEINPUTSTREAM_CID);
|
||||
NS_DEFINE_NAMED_CID(NS_PROTOCOLPROXYSERVICE_CID);
|
||||
NS_DEFINE_NAMED_CID(NS_STREAMCONVERTERSERVICE_CID);
|
||||
NS_DEFINE_NAMED_CID(NS_PACKAGEDAPPSERVICE_CID);
|
||||
NS_DEFINE_NAMED_CID(NS_PACKAGEDAPPVERIFIER_CID);
|
||||
NS_DEFINE_NAMED_CID(NS_DASHBOARD_CID);
|
||||
#ifdef NECKO_PROTOCOL_ftp
|
||||
NS_DEFINE_NAMED_CID(NS_FTPDIRLISTINGCONVERTER_CID);
|
||||
@ -880,6 +883,7 @@ static const mozilla::Module::CIDEntry kNeckoCIDs[] = {
|
||||
{ &kNS_PROTOCOLPROXYSERVICE_CID, true, nullptr, nsProtocolProxyServiceConstructor },
|
||||
{ &kNS_STREAMCONVERTERSERVICE_CID, false, nullptr, CreateNewStreamConvServiceFactory },
|
||||
{ &kNS_PACKAGEDAPPSERVICE_CID, false, NULL, mozilla::net::PackagedAppServiceConstructor },
|
||||
{ &kNS_PACKAGEDAPPVERIFIER_CID, false, NULL, mozilla::net::PackagedAppVerifierConstructor },
|
||||
{ &kNS_DASHBOARD_CID, false, nullptr, mozilla::net::DashboardConstructor },
|
||||
#ifdef NECKO_PROTOCOL_ftp
|
||||
{ &kNS_FTPDIRLISTINGCONVERTER_CID, false, nullptr, CreateNewFTPDirListingConv },
|
||||
@ -1030,6 +1034,7 @@ static const mozilla::Module::ContractIDEntry kNeckoContracts[] = {
|
||||
{ NS_PROTOCOLPROXYSERVICE_CONTRACTID, &kNS_PROTOCOLPROXYSERVICE_CID },
|
||||
{ NS_STREAMCONVERTERSERVICE_CONTRACTID, &kNS_STREAMCONVERTERSERVICE_CID },
|
||||
{ NS_PACKAGEDAPPSERVICE_CONTRACTID, &kNS_PACKAGEDAPPSERVICE_CID },
|
||||
{ NS_PACKAGEDAPPVERIFIER_CONTRACTID, &kNS_PACKAGEDAPPVERIFIER_CID },
|
||||
{ NS_DASHBOARD_CONTRACTID, &kNS_DASHBOARD_CID },
|
||||
#ifdef NECKO_PROTOCOL_ftp
|
||||
{ NS_ISTREAMCONVERTER_KEY FTP_TO_INDEX, &kNS_FTPDIRLISTINGCONVERTER_CID },
|
||||
|
@ -169,6 +169,75 @@ HeaderCopier::ShouldCopy(const nsACString &aHeader) const
|
||||
return true;
|
||||
}
|
||||
|
||||
// Helper function to get the package cache entry from the request. The request
|
||||
// could be from multipart channel or the package channel.
|
||||
static already_AddRefed<nsICacheEntry>
|
||||
GetPackageCacheEntry(nsIRequest *aRequest)
|
||||
{
|
||||
nsCOMPtr<nsIChannel> baseChannel;
|
||||
|
||||
nsCOMPtr<nsIMultiPartChannel> multiChannel(do_QueryInterface(aRequest));
|
||||
if (multiChannel) {
|
||||
// If it's a request from multipart channel, get the base channel from it.
|
||||
multiChannel->GetBaseChannel(getter_AddRefs(baseChannel));
|
||||
} else {
|
||||
// Otherwise, the request is from the package channel.
|
||||
baseChannel = do_QueryInterface(aRequest);
|
||||
}
|
||||
|
||||
if (!baseChannel) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsICachingChannel> cachingChannel = do_QueryInterface(baseChannel);
|
||||
if (!cachingChannel) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsISupports> cacheToken;
|
||||
cachingChannel->GetCacheToken(getter_AddRefs(cacheToken));
|
||||
if (!cacheToken) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsICacheEntry> entry(do_QueryInterface(cacheToken));
|
||||
|
||||
return entry.forget();
|
||||
}
|
||||
|
||||
// Create nsIInputStream based on the given string which doesn't have to
|
||||
// be null-terminated. Note that the string data is shared.
|
||||
static already_AddRefed<nsIInputStream>
|
||||
CreateSharedStringStream(const char* aData, uint32_t aCount)
|
||||
{
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsIStringInputStream> stream =
|
||||
do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv);
|
||||
NS_ENSURE_SUCCESS(rv, nullptr);
|
||||
|
||||
rv = stream->ShareData((char*)aData, aCount);
|
||||
NS_ENSURE_SUCCESS(rv, nullptr);
|
||||
|
||||
return stream.forget();
|
||||
}
|
||||
|
||||
// Get the original HTTP response header from the request.
|
||||
static bool
|
||||
GetOriginalResponseHeader(nsIRequest* aRequest, nsACString& aHeader)
|
||||
{
|
||||
// TODO: The flattened http header might be different from the original.
|
||||
// See Bug 1198669 for further information.
|
||||
|
||||
nsCOMPtr<nsIResponseHeadProvider> headerProvider(do_QueryInterface(aRequest));
|
||||
nsHttpResponseHead *responseHead = headerProvider->GetResponseHead();
|
||||
NS_ENSURE_TRUE(responseHead, false);
|
||||
|
||||
responseHead->Flatten(aHeader, true);
|
||||
aHeader.Append("\r\n");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // anon
|
||||
|
||||
/* static */ nsresult
|
||||
@ -189,17 +258,12 @@ PackagedAppService::CacheEntryWriter::CopyHeadersFromChannel(nsIChannel *aChanne
|
||||
}
|
||||
|
||||
NS_METHOD
|
||||
PackagedAppService::CacheEntryWriter::ConsumeData(nsIInputStream *aStream,
|
||||
void *aClosure,
|
||||
const char *aFromRawSegment,
|
||||
uint32_t aToOffset,
|
||||
PackagedAppService::CacheEntryWriter::ConsumeData(const char *aBuf,
|
||||
uint32_t aCount,
|
||||
uint32_t *aWriteCount)
|
||||
{
|
||||
MOZ_ASSERT(aClosure, "The closure must not be null");
|
||||
CacheEntryWriter *self = static_cast<CacheEntryWriter*>(aClosure);
|
||||
MOZ_ASSERT(self->mOutputStream, "The stream should not be null");
|
||||
return self->mOutputStream->Write(aFromRawSegment, aCount, aWriteCount);
|
||||
MOZ_ASSERT(mOutputStream, "The stream should not be null");
|
||||
return mOutputStream->Write(aBuf, aCount, aWriteCount);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
@ -275,12 +339,8 @@ PackagedAppService::CacheEntryWriter::OnDataAvailable(nsIRequest *aRequest,
|
||||
uint64_t aOffset,
|
||||
uint32_t aCount)
|
||||
{
|
||||
if (!aInputStream) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
// Calls ConsumeData to read the data into the cache entry
|
||||
uint32_t n;
|
||||
return aInputStream->ReadSegments(ConsumeData, this, aCount, &n);
|
||||
MOZ_ASSERT_UNREACHABLE("This function should never ever be called");
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@ -300,6 +360,26 @@ PackagedAppService::PackagedAppChannelListener::OnStartRequest(nsIRequest *aRequ
|
||||
mDownloader->SetIsFromCache(isFromCache);
|
||||
LOG(("[%p] Downloader isFromCache: %d\n", mDownloader.get(), isFromCache));
|
||||
|
||||
// If the package is loaded from cache, check the meta data in the cache
|
||||
// to know if it's a signed package. Notify requesters if it's signed.
|
||||
if (isFromCache) {
|
||||
bool isPackageSigned = false;
|
||||
nsCString signedPackageOrigin;
|
||||
nsCOMPtr<nsICacheEntry> packageCacheEntry = GetPackageCacheEntry(aRequest);
|
||||
if (packageCacheEntry) {
|
||||
const char* key = PackagedAppVerifier::kSignedPakOriginMetadataKey;
|
||||
nsXPIDLCString value;
|
||||
nsresult rv = packageCacheEntry->GetMetaDataElement(key,
|
||||
getter_Copies(value));
|
||||
isPackageSigned = (NS_SUCCEEDED(rv) && !value.IsEmpty());
|
||||
signedPackageOrigin = value;
|
||||
}
|
||||
if (isPackageSigned) {
|
||||
LOG(("The cached package is signed. Notify the requesters."));
|
||||
mDownloader->NotifyOnStartSignedPackageRequest(signedPackageOrigin);
|
||||
}
|
||||
}
|
||||
|
||||
// XXX: This is the place to suspend the channel, doom existing cache entries
|
||||
// for previous resources, and then resume the channel.
|
||||
return mListener->OnStartRequest(aRequest, aContext);
|
||||
@ -326,11 +406,14 @@ PackagedAppService::PackagedAppChannelListener::OnDataAvailable(nsIRequest *aReq
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
NS_IMPL_ISUPPORTS(PackagedAppService::PackagedAppDownloader, nsIStreamListener)
|
||||
NS_IMPL_ISUPPORTS(PackagedAppService::PackagedAppDownloader,
|
||||
nsIStreamListener,
|
||||
nsIPackagedAppVerifierListener)
|
||||
|
||||
nsresult
|
||||
PackagedAppService::PackagedAppDownloader::Init(nsILoadContextInfo* aInfo,
|
||||
const nsCString& aKey)
|
||||
const nsCString& aKey,
|
||||
const nsACString& aPackageOrigin)
|
||||
{
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsICacheStorageService> cacheStorageService =
|
||||
@ -346,9 +429,30 @@ PackagedAppService::PackagedAppDownloader::Init(nsILoadContextInfo* aInfo,
|
||||
}
|
||||
|
||||
mPackageKey = aKey;
|
||||
mPackageOrigin = aPackageOrigin;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
PackagedAppService::PackagedAppDownloader::EnsureVerifier(nsIRequest *aRequest)
|
||||
{
|
||||
if (mVerifier) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG(("Creating PackagedAppVerifier."));
|
||||
|
||||
nsCOMPtr<nsIMultiPartChannel> multiChannel(do_QueryInterface(aRequest));
|
||||
nsCString signature = GetSignatureFromChannel(multiChannel);
|
||||
nsCOMPtr<nsICacheEntry> packageCacheEntry = GetPackageCacheEntry(aRequest);
|
||||
|
||||
mVerifier = new PackagedAppVerifier(this,
|
||||
mPackageOrigin,
|
||||
signature,
|
||||
packageCacheEntry);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
PackagedAppService::PackagedAppDownloader::OnStartRequest(nsIRequest *aRequest,
|
||||
nsISupports *aContext)
|
||||
@ -374,7 +478,18 @@ PackagedAppService::PackagedAppDownloader::OnStartRequest(nsIRequest *aRequest,
|
||||
MOZ_ASSERT(mWriter);
|
||||
rv = mWriter->OnStartRequest(aRequest, aContext);
|
||||
NS_WARN_IF(NS_FAILED(rv));
|
||||
return NS_OK;
|
||||
|
||||
EnsureVerifier(aRequest);
|
||||
mVerifier->OnStartRequest(nullptr, uri);
|
||||
|
||||
// Since the header is considered as a part of the streaming data,
|
||||
// we need to feed the header as data to the verifier.
|
||||
nsCString header;
|
||||
if (!GetOriginalResponseHeader(aRequest, header)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
nsCOMPtr<nsIInputStream> stream = CreateSharedStringStream(header.get(), header.Length());
|
||||
return mVerifier->OnDataAvailable(nullptr, nullptr, stream, 0, header.Length());
|
||||
}
|
||||
|
||||
nsresult
|
||||
@ -442,6 +557,56 @@ PackagedAppService::PackagedAppDownloader::GetSubresourceURI(nsIRequest * aReque
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
PackagedAppService::PackagedAppDownloader::OnError(EErrorType aError)
|
||||
{
|
||||
// TODO: Handler verification error properly.
|
||||
LOG(("PackagedAppDownloader::OnError > %d", aError));
|
||||
|
||||
FinalizeDownload(NS_ERROR_SIGNED_APP_MANIFEST_INVALID);
|
||||
}
|
||||
|
||||
void
|
||||
PackagedAppService::PackagedAppDownloader::FinalizeDownload(nsresult aStatusCode)
|
||||
{
|
||||
// If this is the last part of the package, it means the requested resources
|
||||
// have not been found in the package so we return an appropriate error.
|
||||
// If the package response comes from the cache, we want to preserve the
|
||||
// statusCode, so ClearCallbacks looks for the resource in the cache, instead
|
||||
// of returning NS_ERROR_FILE_NOT_FOUND.
|
||||
if (NS_SUCCEEDED(aStatusCode) && !mIsFromCache) {
|
||||
aStatusCode = NS_ERROR_FILE_NOT_FOUND;
|
||||
}
|
||||
|
||||
nsRefPtr<PackagedAppDownloader> kungFuDeathGrip(this);
|
||||
// NotifyPackageDownloaded removes the ref from the array. Keep a temp ref
|
||||
if (gPackagedAppService) {
|
||||
gPackagedAppService->NotifyPackageDownloaded(mPackageKey);
|
||||
}
|
||||
ClearCallbacks(aStatusCode);
|
||||
|
||||
mVerifier = nullptr;
|
||||
}
|
||||
|
||||
nsCString
|
||||
PackagedAppService::PackagedAppDownloader::GetSignatureFromChannel(nsIMultiPartChannel* aMulitChannel)
|
||||
{
|
||||
if (mIsFromCache) {
|
||||
// We don't need the signature if the resource is loaded from cache.
|
||||
return EmptyCString();
|
||||
}
|
||||
|
||||
if (!aMulitChannel) {
|
||||
LOG(("The package is either not loaded from cache or malformed."));
|
||||
return EmptyCString();
|
||||
}
|
||||
|
||||
nsCString packageHeader;
|
||||
aMulitChannel->GetPreamble(packageHeader);
|
||||
|
||||
return packageHeader;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
PackagedAppService::PackagedAppDownloader::OnStopRequest(nsIRequest *aRequest,
|
||||
nsISupports *aContext,
|
||||
@ -453,55 +618,82 @@ PackagedAppService::PackagedAppDownloader::OnStopRequest(nsIRequest *aRequest,
|
||||
LOG(("[%p] PackagedAppDownloader::OnStopRequest > status:%X multiChannel:%p\n",
|
||||
this, aStatusCode, multiChannel.get()));
|
||||
|
||||
// The request is normally a multiPartChannel. If it isn't, it generally means
|
||||
// an error has occurred in nsMultiMixedConv.
|
||||
// If an error occurred in OnStartRequest, mWriter could be null.
|
||||
if (multiChannel && mWriter) {
|
||||
mWriter->OnStopRequest(aRequest, aContext, aStatusCode);
|
||||
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
rv = GetSubresourceURI(aRequest, getter_AddRefs(uri));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsICacheEntry> entry;
|
||||
mWriter->mEntry.swap(entry);
|
||||
|
||||
// We don't need the writer anymore - this will close its stream
|
||||
mWriter = nullptr;
|
||||
CallCallbacks(uri, entry, aStatusCode);
|
||||
}
|
||||
|
||||
// lastPart will be true if this is the last part in the package,
|
||||
// or if aRequest isn't a multipart channel
|
||||
bool lastPart = true;
|
||||
if (multiChannel) {
|
||||
rv = multiChannel->GetIsLastPart(&lastPart);
|
||||
if (NS_SUCCEEDED(rv) && !lastPart) {
|
||||
// If this isn't the last part, we don't do the cleanup yet
|
||||
return NS_OK;
|
||||
multiChannel->GetIsLastPart(&lastPart);
|
||||
}
|
||||
|
||||
// The request is normally a multiPartChannel. If it isn't, it generally means
|
||||
// an error has occurred in nsMultiMixedConv.
|
||||
// If an error occurred in OnStartRequest, mWriter could be null.
|
||||
if (!multiChannel || !mWriter) {
|
||||
LOG(("Either the package was loaded from cache or malformed"));
|
||||
if (lastPart) {
|
||||
// Chances to get here:
|
||||
// 1) Very likely the package has been cached or
|
||||
// 2) Less likely the package is malformed.
|
||||
FinalizeDownload(aStatusCode);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// If this is the last part of the package, it means the requested resources
|
||||
// have not been found in the package so we return an appropriate error.
|
||||
// If the package response comes from the cache, we want to preserve the
|
||||
// statusCode, so ClearCallbacks looks for the resource in the cache, instead
|
||||
// of returning NS_ERROR_FILE_NOT_FOUND.
|
||||
if (NS_SUCCEEDED(aStatusCode) && lastPart && !mIsFromCache) {
|
||||
aStatusCode = NS_ERROR_FILE_NOT_FOUND;
|
||||
LOG(("We are going to finish the resource and process it in the verifier."));
|
||||
|
||||
// We've got a resource downloaded. Finalize this resource cache and delegate to
|
||||
// PackagedAppVerifier rather than serving this resource right away.
|
||||
mWriter->OnStopRequest(aRequest, aContext, aStatusCode);
|
||||
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
rv = GetSubresourceURI(aRequest, getter_AddRefs(uri));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsRefPtr<PackagedAppDownloader> kungFuDeathGrip(this);
|
||||
// NotifyPackageDownloaded removes the ref from the array. Keep a temp ref
|
||||
if (gPackagedAppService) {
|
||||
gPackagedAppService->NotifyPackageDownloaded(mPackageKey);
|
||||
}
|
||||
ClearCallbacks(aStatusCode);
|
||||
nsCOMPtr<nsICacheEntry> entry;
|
||||
mWriter->mEntry.swap(entry);
|
||||
|
||||
// We don't need the writer anymore - this will close its stream
|
||||
mWriter = nullptr;
|
||||
|
||||
// The downloader only needs to focus on PackagedAppVerifierListener callback.
|
||||
// The PackagedAppVerifier would handle the manifest/resource verification.
|
||||
nsRefPtr<ResourceCacheInfo> info =
|
||||
new ResourceCacheInfo(uri, entry, aStatusCode, lastPart);
|
||||
|
||||
mVerifier->OnStopRequest(nullptr, info, aStatusCode);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_METHOD
|
||||
PackagedAppService::PackagedAppDownloader::ConsumeData(nsIInputStream *aStream,
|
||||
void *aClosure,
|
||||
const char *aFromRawSegment,
|
||||
uint32_t aToOffset,
|
||||
uint32_t aCount,
|
||||
uint32_t *aWriteCount)
|
||||
{
|
||||
MOZ_ASSERT(aClosure, "The closure must not be null");
|
||||
|
||||
if (!aStream) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
PackagedAppDownloader *self = static_cast<PackagedAppDownloader*>(aClosure);
|
||||
|
||||
if (!self->mWriter) {
|
||||
*aWriteCount = aCount;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
self->mWriter->ConsumeData(aFromRawSegment, aCount, aWriteCount);
|
||||
|
||||
nsCOMPtr<nsIInputStream> stream = CreateSharedStringStream(aFromRawSegment, aCount);
|
||||
return self->mVerifier->OnDataAvailable(nullptr, nullptr, stream, 0, aCount);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
PackagedAppService::PackagedAppDownloader::OnDataAvailable(nsIRequest *aRequest,
|
||||
nsISupports *aContext,
|
||||
@ -509,12 +701,8 @@ PackagedAppService::PackagedAppDownloader::OnDataAvailable(nsIRequest *aRequest,
|
||||
uint64_t aOffset,
|
||||
uint32_t aCount)
|
||||
{
|
||||
if (!mWriter) {
|
||||
uint32_t n;
|
||||
return aInputStream->ReadSegments(NS_DiscardSegment, nullptr, aCount, &n);
|
||||
}
|
||||
return mWriter->OnDataAvailable(aRequest, aContext, aInputStream, aOffset,
|
||||
aCount);
|
||||
uint32_t n;
|
||||
return aInputStream->ReadSegments(ConsumeData, this, aCount, &n);
|
||||
}
|
||||
|
||||
nsresult
|
||||
@ -536,6 +724,12 @@ PackagedAppService::PackagedAppDownloader::AddCallback(nsIURI *aURI,
|
||||
// need to wait for it to be inserted in the cache and we can serve it
|
||||
// right now, directly. See also the CallCallbacks method bellow.
|
||||
LOG(("[%p] > already downloaded\n", this));
|
||||
|
||||
// This is the case where a package downloader is still running and we
|
||||
// peek data from it.
|
||||
|
||||
// TODO: Bug 1186290 to notify that the signed packaged content is ready
|
||||
// to load.
|
||||
mCacheStorage->AsyncOpenURI(aURI, EmptyCString(),
|
||||
nsICacheStorage::OPEN_READONLY, aCallback);
|
||||
} else {
|
||||
@ -572,6 +766,7 @@ PackagedAppService::PackagedAppDownloader::CallCallbacks(nsIURI *aURI,
|
||||
|
||||
nsCOMArray<nsICacheEntryOpenCallback>* array = mCallbacks.Get(spec);
|
||||
if (array) {
|
||||
uint32_t callbacksNum = array->Length();
|
||||
// Call all the callbacks for this URI
|
||||
for (uint32_t i = 0; i < array->Length(); ++i) {
|
||||
nsCOMPtr<nsICacheEntryOpenCallback> callback(array->ObjectAt(i));
|
||||
@ -583,7 +778,7 @@ PackagedAppService::PackagedAppDownloader::CallCallbacks(nsIURI *aURI,
|
||||
// An empty array means that the resource was already downloaded, and a
|
||||
// new call to AddCallback can simply return it from the cache.
|
||||
array->Clear();
|
||||
LOG(("[%p] > called callbacks\n", this));
|
||||
LOG(("[%p] > called callbacks (%d)\n", this, callbacksNum));
|
||||
} else {
|
||||
// There were no listeners waiting for this resource, but we insert a new
|
||||
// empty array into the hashtable so if any new callbacks are added while
|
||||
@ -605,6 +800,8 @@ PackagedAppService::PackagedAppDownloader::ClearCallbacks(nsresult aResult)
|
||||
LOG(("[%p] PackagedAppService::PackagedAppDownloader::ClearCallbacks > packageKey:%s status:%X\n",
|
||||
this, mPackageKey.get(), aResult));
|
||||
|
||||
// Clear the registered callbacks which are not called at all. If the package is already
|
||||
// in the cache, the requested resource will be called back here.
|
||||
for (auto iter = mCallbacks.Iter(); !iter.Done(); iter.Next()) {
|
||||
const nsACString& key = iter.Key();
|
||||
const nsCOMArray<nsICacheEntryOpenCallback>* callbackArray = iter.UserData();
|
||||
@ -640,6 +837,84 @@ PackagedAppService::PackagedAppDownloader::ClearCallbacks(nsresult aResult)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
PackagedAppService::PackagedAppDownloader::NotifyOnStartSignedPackageRequest(const nsACString& aPackageOrigin)
|
||||
{
|
||||
// TODO: Bug 1186290 to notify whoever wants to know when the signed package is
|
||||
// about to load.
|
||||
LOG(("Notifying the signed package is ready to load."));
|
||||
}
|
||||
|
||||
void PackagedAppService::PackagedAppDownloader::InstallSignedPackagedApp()
|
||||
{
|
||||
// TODO: Bug 1178533 to register permissions, system messages etc on navigation to
|
||||
// signed packages.
|
||||
LOG(("Install this packaged app."));
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------
|
||||
// nsIPackagedAppVerifierListener
|
||||
//------------------------------------------------------------------
|
||||
NS_IMETHODIMP
|
||||
PackagedAppService::PackagedAppDownloader::OnVerified(bool aIsManifest,
|
||||
nsIURI* aUri,
|
||||
nsICacheEntry* aCacheEntry,
|
||||
nsresult aStatusCode,
|
||||
bool aIsLastPart,
|
||||
bool aVerificationSuccess)
|
||||
{
|
||||
RefPtr<ResourceCacheInfo> info =
|
||||
new ResourceCacheInfo(aUri, aCacheEntry, aStatusCode, aIsLastPart);
|
||||
|
||||
aIsManifest ? OnManifestVerified(info, aVerificationSuccess)
|
||||
: OnResourceVerified(info, aVerificationSuccess);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
PackagedAppService::PackagedAppDownloader::OnManifestVerified(const ResourceCacheInfo* aInfo,
|
||||
bool aSuccess)
|
||||
{
|
||||
if (!aSuccess) {
|
||||
// The signature is found but not verified.
|
||||
return OnError(ERROR_MANIFEST_VERIFIED_FAILED);
|
||||
}
|
||||
|
||||
// TODO: If we disallow the request for the manifest file, do NOT callback here.
|
||||
CallCallbacks(aInfo->mURI, aInfo->mCacheEntry, aInfo->mStatusCode);
|
||||
|
||||
bool isPackagedSigned;
|
||||
mVerifier->GetIsPackageSigned(&isPackagedSigned);
|
||||
if (!isPackagedSigned) {
|
||||
// A verified but unsigned manifest means this package has no signature.
|
||||
LOG(("No signature in the package. Just run normally."));
|
||||
return;
|
||||
}
|
||||
|
||||
nsCString packageOrigin;
|
||||
mVerifier->GetPackageOrigin(packageOrigin);
|
||||
NotifyOnStartSignedPackageRequest(packageOrigin);
|
||||
InstallSignedPackagedApp();
|
||||
}
|
||||
|
||||
void
|
||||
PackagedAppService::PackagedAppDownloader::OnResourceVerified(const ResourceCacheInfo* aInfo,
|
||||
bool aSuccess)
|
||||
{
|
||||
if (!aSuccess) {
|
||||
return OnError(ERROR_RESOURCE_VERIFIED_FAILED);
|
||||
}
|
||||
|
||||
// Serve this resource to all listeners.
|
||||
CallCallbacks(aInfo->mURI, aInfo->mCacheEntry, aInfo->mStatusCode);
|
||||
|
||||
if (aInfo->mIsLastPart) {
|
||||
LOG(("This is the last part. FinalizeDownload (%d)", aInfo->mStatusCode));
|
||||
FinalizeDownload(aInfo->mStatusCode);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
PackagedAppService::PackagedAppService()
|
||||
@ -786,7 +1061,9 @@ PackagedAppService::GetResource(nsIChannel *aChannel,
|
||||
}
|
||||
|
||||
downloader = new PackagedAppDownloader();
|
||||
rv = downloader->Init(loadContextInfo, key);
|
||||
nsCString packageOrigin;
|
||||
principal->GetOriginNoSuffix(packageOrigin);
|
||||
rv = downloader->Init(loadContextInfo, key, packageOrigin);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
@ -10,6 +10,9 @@
|
||||
#include "nsIPackagedAppService.h"
|
||||
#include "nsILoadContextInfo.h"
|
||||
#include "nsICacheStorage.h"
|
||||
#include "PackagedAppVerifier.h"
|
||||
#include "nsIMultiPartChannel.h"
|
||||
#include "PackagedAppVerifier.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace net {
|
||||
@ -61,6 +64,12 @@ private:
|
||||
static nsresult Create(nsIURI*, nsICacheStorage*, CacheEntryWriter**);
|
||||
|
||||
nsCOMPtr<nsICacheEntry> mEntry;
|
||||
|
||||
// Called by PackagedAppDownloader to write data to the cache entry.
|
||||
NS_METHOD ConsumeData(const char *aBuf,
|
||||
uint32_t aCount,
|
||||
uint32_t *aWriteCount);
|
||||
|
||||
private:
|
||||
CacheEntryWriter() { }
|
||||
~CacheEntryWriter() { }
|
||||
@ -73,11 +82,6 @@ private:
|
||||
static nsresult CopyHeadersFromChannel(nsIChannel *aChannel,
|
||||
nsHttpResponseHead *aHead);
|
||||
|
||||
// Static method used to write data into the cache entry
|
||||
// Called from OnDataAvailable
|
||||
static NS_METHOD ConsumeData(nsIInputStream *in, void *closure,
|
||||
const char *fromRawSegment, uint32_t toOffset,
|
||||
uint32_t count, uint32_t *writeCount);
|
||||
// We write the data we read from the network into this stream which goes
|
||||
// to the cache entry.
|
||||
nsCOMPtr<nsIOutputStream> mOutputStream;
|
||||
@ -93,28 +97,83 @@ private:
|
||||
// NotifyPackageDownloaded(packageURI), so the service releases the ref.
|
||||
class PackagedAppDownloader final
|
||||
: public nsIStreamListener
|
||||
, public nsIPackagedAppVerifierListener
|
||||
{
|
||||
public:
|
||||
typedef PackagedAppVerifier::ResourceCacheInfo ResourceCacheInfo;
|
||||
|
||||
private:
|
||||
enum EErrorType {
|
||||
ERROR_MANIFEST_VERIFIED_FAILED,
|
||||
ERROR_RESOURCE_VERIFIED_FAILED,
|
||||
};
|
||||
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSISTREAMLISTENER
|
||||
NS_DECL_NSIREQUESTOBSERVER
|
||||
NS_DECL_NSIPACKAGEDAPPVERIFIERLISTENER
|
||||
|
||||
// Initializes mCacheStorage and saves aKey as mPackageKey which is later
|
||||
// used to remove this object from PackagedAppService::mDownloadingPackages
|
||||
// - aKey is a string which uniquely identifies this package within the
|
||||
// packagedAppService
|
||||
nsresult Init(nsILoadContextInfo* aInfo, const nsCString &aKey);
|
||||
nsresult Init(nsILoadContextInfo* aInfo, const nsCString &aKey,
|
||||
const nsACString& aPackageOrigin);
|
||||
// Registers a callback which gets called when the given nsIURI is downloaded
|
||||
// aURI is the full URI of a subresource, composed of packageURI + !// + subresourcePath
|
||||
nsresult AddCallback(nsIURI *aURI, nsICacheEntryOpenCallback *aCallback);
|
||||
|
||||
// Remove the callback from the resource callback list.
|
||||
nsresult RemoveCallbacks(nsICacheEntryOpenCallback* aCallback);
|
||||
|
||||
// Called by PackagedAppChannelListener to note the fact that the package
|
||||
// is coming from the cache, and no subresources are to be expected as only
|
||||
// package metadata is saved in the cache.
|
||||
void SetIsFromCache(bool aFromCache) { mIsFromCache = aFromCache; }
|
||||
|
||||
// Notify the observers who are interested in knowing a signed packaged content
|
||||
// is about to load from either HTTP or cache..
|
||||
void NotifyOnStartSignedPackageRequest(const nsACString& PackageOrigin);
|
||||
|
||||
private:
|
||||
~PackagedAppDownloader() { }
|
||||
|
||||
// Static method used to write data into the cache entry or discard
|
||||
// if there's no writer. Used as a writer function of
|
||||
// nsIInputStream::ReadSegments.
|
||||
static NS_METHOD ConsumeData(nsIInputStream *aStream,
|
||||
void *aClosure,
|
||||
const char *aFromRawSegment,
|
||||
uint32_t aToOffset,
|
||||
uint32_t aCount,
|
||||
uint32_t *aWriteCount);
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// For PackagedAppVerifierListener.
|
||||
//---------------------------------------------------------------
|
||||
virtual void OnManifestVerified(const ResourceCacheInfo* aInfo, bool aSuccess);
|
||||
virtual void OnResourceVerified(const ResourceCacheInfo* aInfo, bool aSuccess);
|
||||
|
||||
// Handle all kinds of error during package downloading.
|
||||
void OnError(EErrorType aError);
|
||||
|
||||
// Called when the last part is complete or the resource is from cache.
|
||||
void FinalizeDownload(nsresult aStatusCode);
|
||||
|
||||
// Get the signature from the multipart channel.
|
||||
nsCString GetSignatureFromChannel(nsIMultiPartChannel* aChannel);
|
||||
|
||||
// Start off a resource hash computation and feed the HTTP response header.
|
||||
nsresult BeginHashComputation(nsIURI* aURI, nsIRequest* aRequest);
|
||||
|
||||
// Ensure a packaged app verifier is created.
|
||||
void EnsureVerifier(nsIRequest *aRequest);
|
||||
|
||||
// Handle all tasks about app installation like permission and system message
|
||||
// registration.
|
||||
void InstallSignedPackagedApp();
|
||||
|
||||
// Calls all the callbacks registered for the given URI.
|
||||
// aURI is the full URI of a subresource, composed of packageURI + !// + subresourcePath
|
||||
// It passes the cache entry and the result when calling OnCacheEntryAvailable
|
||||
@ -144,6 +203,14 @@ private:
|
||||
|
||||
// Whether the package is from the cache
|
||||
bool mIsFromCache;
|
||||
|
||||
// Deal with verification and delegate callbacks to the downloader.
|
||||
nsRefPtr<PackagedAppVerifier> mVerifier;
|
||||
|
||||
// The package origin without signed package origin identifier.
|
||||
// If you need the origin with the signity taken into account, use
|
||||
// PackagedAppVerifier::GetPackageOrigin().
|
||||
nsCString mPackageOrigin;
|
||||
};
|
||||
|
||||
// Intercepts OnStartRequest, OnDataAvailable*, OnStopRequest method calls
|
||||
|
290
netwerk/protocol/http/PackagedAppVerifier.cpp
Normal file
290
netwerk/protocol/http/PackagedAppVerifier.cpp
Normal file
@ -0,0 +1,290 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set sw=2 ts=8 et tw=80 : */
|
||||
/* 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/. */
|
||||
|
||||
#include "nsICacheStorage.h"
|
||||
#include "nsICacheStorageService.h"
|
||||
#include "../../cache2/CacheFileUtils.h"
|
||||
#include "mozilla/Logging.h"
|
||||
#include "mozilla/DebugOnly.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "PackagedAppVerifier.h"
|
||||
#include "nsITimer.h"
|
||||
#include "nsIPackagedAppVerifier.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
|
||||
static const short kResourceHashType = nsICryptoHash::SHA256;
|
||||
|
||||
// If it's true, all the verification will be skipped and the package will
|
||||
// be treated signed.
|
||||
static bool gDeveloperMode = false;
|
||||
|
||||
namespace mozilla {
|
||||
namespace net {
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
NS_IMPL_ISUPPORTS(PackagedAppVerifier, nsIPackagedAppVerifier)
|
||||
|
||||
NS_IMPL_ISUPPORTS(PackagedAppVerifier::ResourceCacheInfo, nsISupports)
|
||||
|
||||
const char* PackagedAppVerifier::kSignedPakOriginMetadataKey = "signed-pak-origin";
|
||||
|
||||
PackagedAppVerifier::PackagedAppVerifier()
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(NS_IsMainThread(),
|
||||
"PackagedAppVerifier::OnResourceVerified must be on main thread");
|
||||
|
||||
Init(nullptr, EmptyCString(), EmptyCString(), nullptr);
|
||||
}
|
||||
|
||||
PackagedAppVerifier::PackagedAppVerifier(nsIPackagedAppVerifierListener* aListener,
|
||||
const nsACString& aPackageOrigin,
|
||||
const nsACString& aSignature,
|
||||
nsICacheEntry* aPackageCacheEntry)
|
||||
{
|
||||
Init(aListener, aPackageOrigin, aSignature, aPackageCacheEntry);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP PackagedAppVerifier::Init(nsIPackagedAppVerifierListener* aListener,
|
||||
const nsACString& aPackageOrigin,
|
||||
const nsACString& aSignature,
|
||||
nsICacheEntry* aPackageCacheEntry)
|
||||
{
|
||||
static bool onceThru = false;
|
||||
if (!onceThru) {
|
||||
Preferences::AddBoolVarCache(&gDeveloperMode,
|
||||
"network.http.packaged-apps-developer-mode", false);
|
||||
onceThru = true;
|
||||
}
|
||||
|
||||
mListener = aListener;
|
||||
mState = STATE_UNKNOWN;
|
||||
mPackageOrigin = aPackageOrigin;
|
||||
mSignature = aSignature;
|
||||
mIsPackageSigned = false;
|
||||
mPackageCacheEntry = aPackageCacheEntry;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// nsIStreamListener
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
// @param aRequest nullptr.
|
||||
// @param aContext The URI of the resource. (nsIURI)
|
||||
NS_IMETHODIMP
|
||||
PackagedAppVerifier::OnStartRequest(nsIRequest *aRequest,
|
||||
nsISupports *aContext)
|
||||
{
|
||||
if (!mHasher) {
|
||||
mHasher = do_CreateInstance("@mozilla.org/security/hash;1");
|
||||
}
|
||||
|
||||
NS_ENSURE_TRUE(mHasher, NS_ERROR_FAILURE);
|
||||
|
||||
nsCOMPtr<nsIURI> uri = do_QueryInterface(aContext);
|
||||
NS_ENSURE_TRUE(uri, NS_ERROR_FAILURE);
|
||||
uri->GetAsciiSpec(mHashingResourceURI);
|
||||
|
||||
return mHasher->Init(kResourceHashType);
|
||||
}
|
||||
|
||||
// @param aRequest nullptr.
|
||||
// @param aContext nullptr.
|
||||
// @param aInputStream as-is.
|
||||
// @param aOffset as-is.
|
||||
// @param aCount as-is.
|
||||
NS_IMETHODIMP
|
||||
PackagedAppVerifier::OnDataAvailable(nsIRequest *aRequest,
|
||||
nsISupports *aContext,
|
||||
nsIInputStream *aInputStream,
|
||||
uint64_t aOffset,
|
||||
uint32_t aCount)
|
||||
{
|
||||
MOZ_ASSERT(!mHashingResourceURI.IsEmpty(), "MUST call BeginResourceHash first.");
|
||||
NS_ENSURE_TRUE(mHasher, NS_ERROR_FAILURE);
|
||||
return mHasher->UpdateFromStream(aInputStream, aCount);
|
||||
}
|
||||
|
||||
// @param aRequest nullptr.
|
||||
// @param aContext The resource cache info.
|
||||
// @param aStatusCode as-is,
|
||||
NS_IMETHODIMP
|
||||
PackagedAppVerifier::OnStopRequest(nsIRequest* aRequest,
|
||||
nsISupports* aContext,
|
||||
nsresult aStatusCode)
|
||||
{
|
||||
NS_ENSURE_TRUE(mHasher, NS_ERROR_FAILURE);
|
||||
|
||||
nsresult rv = mHasher->Finish(true, mLastComputedResourceHash);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
LOG(("Hash of %s is %s", mHashingResourceURI.get(),
|
||||
mLastComputedResourceHash.get()));
|
||||
|
||||
ProcessResourceCache(static_cast<ResourceCacheInfo*>(aContext));
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
PackagedAppVerifier::ProcessResourceCache(const ResourceCacheInfo* aInfo)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(NS_IsMainThread(), "ProcessResourceCache must be on main thread");
|
||||
|
||||
switch (mState) {
|
||||
case STATE_UNKNOWN:
|
||||
// The first resource has to be the manifest.
|
||||
VerifyManifest(aInfo);
|
||||
break;
|
||||
|
||||
case STATE_MANIFEST_VERIFIED_OK:
|
||||
VerifyResource(aInfo);
|
||||
break;
|
||||
|
||||
case STATE_MANIFEST_VERIFIED_FAILED:
|
||||
OnResourceVerified(aInfo, false);
|
||||
break;
|
||||
|
||||
default:
|
||||
MOZ_CRASH("Unexpected PackagedAppVerifier state."); // Shouldn't get here.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
PackagedAppVerifier::VerifyManifest(const ResourceCacheInfo* aInfo)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(NS_IsMainThread(), "Manifest verification must be on main thread");
|
||||
|
||||
LOG(("Ready to verify manifest."));
|
||||
|
||||
if (gDeveloperMode) {
|
||||
LOG(("Developer mode! Bypass verification."));
|
||||
OnManifestVerified(aInfo, true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mSignature.IsEmpty()) {
|
||||
LOG(("No signature. No need to do verification."));
|
||||
OnManifestVerified(aInfo, true);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Implement manifest verification.
|
||||
LOG(("Manifest verification not implemented yet. See Bug 1178518."));
|
||||
OnManifestVerified(aInfo, false);
|
||||
}
|
||||
|
||||
void
|
||||
PackagedAppVerifier::VerifyResource(const ResourceCacheInfo* aInfo)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(NS_IsMainThread(), "Resource verification must be on main thread");
|
||||
|
||||
LOG(("Checking the resource integrity. '%s'", mLastComputedResourceHash.get()));
|
||||
|
||||
if (gDeveloperMode) {
|
||||
LOG(("Developer mode! Bypass integrity check."));
|
||||
OnResourceVerified(aInfo, true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mSignature.IsEmpty()) {
|
||||
LOG(("No signature. No need to do resource integrity check."));
|
||||
OnResourceVerified(aInfo, true);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Implement resource integrity check.
|
||||
LOG(("Resource integrity check not implemented yet. See Bug 1178518."));
|
||||
OnResourceVerified(aInfo, false);
|
||||
}
|
||||
|
||||
void
|
||||
PackagedAppVerifier::OnManifestVerified(const ResourceCacheInfo* aInfo, bool aSuccess)
|
||||
{
|
||||
LOG(("PackagedAppVerifier::OnManifestVerified: %d", aSuccess));
|
||||
|
||||
// Only when the manifest verified and package has signature would we
|
||||
// regard this package is signed.
|
||||
mIsPackageSigned = aSuccess && !mSignature.IsEmpty();
|
||||
|
||||
mState = aSuccess ? STATE_MANIFEST_VERIFIED_OK
|
||||
: STATE_MANIFEST_VERIFIED_FAILED;
|
||||
|
||||
// TODO: Update mPackageOrigin.
|
||||
|
||||
// If the package is signed, add related info to the package cache.
|
||||
if (mIsPackageSigned && mPackageCacheEntry) {
|
||||
LOG(("This package is signed. Add this info to the cache channel."));
|
||||
if (mPackageCacheEntry) {
|
||||
mPackageCacheEntry->SetMetaDataElement(kSignedPakOriginMetadataKey,
|
||||
mPackageOrigin.get());
|
||||
mPackageCacheEntry = nullptr; // the cache entry is no longer needed.
|
||||
}
|
||||
}
|
||||
|
||||
mListener->OnVerified(true, // aIsManifest.
|
||||
aInfo->mURI,
|
||||
aInfo->mCacheEntry,
|
||||
aInfo->mStatusCode,
|
||||
aInfo->mIsLastPart,
|
||||
aSuccess);
|
||||
|
||||
LOG(("PackagedAppVerifier::OnManifestVerified done"));
|
||||
}
|
||||
|
||||
void
|
||||
PackagedAppVerifier::OnResourceVerified(const ResourceCacheInfo* aInfo, bool aSuccess)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(NS_IsMainThread(),
|
||||
"PackagedAppVerifier::OnResourceVerified must be on main thread");
|
||||
|
||||
mListener->OnVerified(false, // aIsManifest.
|
||||
aInfo->mURI,
|
||||
aInfo->mCacheEntry,
|
||||
aInfo->mStatusCode,
|
||||
aInfo->mIsLastPart,
|
||||
aSuccess);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// nsIPackagedAppVerifier.
|
||||
//---------------------------------------------------------------
|
||||
|
||||
NS_IMETHODIMP
|
||||
PackagedAppVerifier::GetPackageOrigin(nsACString& aPackageOrigin)
|
||||
{
|
||||
aPackageOrigin = mPackageOrigin;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
PackagedAppVerifier::GetIsPackageSigned(bool* aIsPackagedSigned)
|
||||
{
|
||||
*aIsPackagedSigned = mIsPackageSigned;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
PackagedAppVerifier::CreateResourceCacheInfo(nsIURI* aUri,
|
||||
nsICacheEntry* aCacheEntry,
|
||||
nsresult aStatusCode,
|
||||
bool aIsLastPart,
|
||||
nsISupports** aReturn)
|
||||
{
|
||||
nsCOMPtr<nsISupports> info =
|
||||
new ResourceCacheInfo(aUri, aCacheEntry, aStatusCode, aIsLastPart);
|
||||
|
||||
info.forget(aReturn);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
||||
} // namespace net
|
||||
} // namespace mozilla
|
139
netwerk/protocol/http/PackagedAppVerifier.h
Normal file
139
netwerk/protocol/http/PackagedAppVerifier.h
Normal file
@ -0,0 +1,139 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set sw=2 ts=8 et tw=80 : */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef mozilla_net_PackagedAppVerifier_h
|
||||
#define mozilla_net_PackagedAppVerifier_h
|
||||
|
||||
#include "nsICacheEntry.h"
|
||||
#include "nsIURI.h"
|
||||
#include "nsClassHashtable.h"
|
||||
#include "nsHashKeys.h"
|
||||
#include "nsICryptoHash.h"
|
||||
#include "nsIPackagedAppVerifier.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace net {
|
||||
|
||||
class PackagedAppVerifier final
|
||||
: public nsIPackagedAppVerifier
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIREQUESTOBSERVER
|
||||
NS_DECL_NSISTREAMLISTENER
|
||||
NS_DECL_NSIPACKAGEDAPPVERIFIER
|
||||
|
||||
public:
|
||||
enum EState {
|
||||
// The initial state.
|
||||
STATE_UNKNOWN,
|
||||
|
||||
// Either the package has no signature or the manifest is verified
|
||||
// successfully will we be in this state.
|
||||
STATE_MANIFEST_VERIFIED_OK,
|
||||
|
||||
// iff the package has signature but the manifest is not well signed.
|
||||
STATE_MANIFEST_VERIFIED_FAILED,
|
||||
|
||||
// The manifest is well signed but the resource integrity check failed.
|
||||
STATE_RESOURCE_VERIFIED_FAILED,
|
||||
};
|
||||
|
||||
// The only reason to inherit from nsISupports is it needs to be
|
||||
// passed as the context to PackagedAppVerifier::OnStopRequest.
|
||||
class ResourceCacheInfo : public nsISupports
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
|
||||
ResourceCacheInfo(nsIURI* aURI,
|
||||
nsICacheEntry* aCacheEntry,
|
||||
nsresult aStatusCode,
|
||||
bool aIsLastPart)
|
||||
: mURI(aURI)
|
||||
, mCacheEntry(aCacheEntry)
|
||||
, mStatusCode(aStatusCode)
|
||||
, mIsLastPart(aIsLastPart)
|
||||
{
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIURI> mURI;
|
||||
nsCOMPtr<nsICacheEntry> mCacheEntry;
|
||||
nsresult mStatusCode;
|
||||
bool mIsLastPart;
|
||||
|
||||
private:
|
||||
virtual ~ResourceCacheInfo() { }
|
||||
};
|
||||
|
||||
public:
|
||||
PackagedAppVerifier();
|
||||
|
||||
PackagedAppVerifier(nsIPackagedAppVerifierListener* aListener,
|
||||
const nsACString& aPackageOrigin,
|
||||
const nsACString& aSignature,
|
||||
nsICacheEntry* aPackageCacheEntry);
|
||||
|
||||
static const char* kSignedPakOriginMetadataKey;
|
||||
|
||||
private:
|
||||
virtual ~PackagedAppVerifier() { }
|
||||
|
||||
// Called when a resource is already fully written in the cache. This resource
|
||||
// will be processed and is guaranteed to be called back in either:
|
||||
//
|
||||
// 1) PackagedAppVerifierListener::OnManifestVerified:
|
||||
// ------------------------------------------------------------------------
|
||||
// If the resource is the first one in the package, it will be called
|
||||
// back in OnManifestVerified no matter this package has a signature or not.
|
||||
//
|
||||
// 2) PackagedAppVerifierListener::OnResourceVerified.
|
||||
// ------------------------------------------------------------------------
|
||||
// Otherwise, the resource will be called back here.
|
||||
//
|
||||
void ProcessResourceCache(const ResourceCacheInfo* aInfo);
|
||||
|
||||
// This two functions would call the actual verifier.
|
||||
void VerifyManifest(const ResourceCacheInfo* aInfo);
|
||||
void VerifyResource(const ResourceCacheInfo* aInfo);
|
||||
|
||||
void OnManifestVerified(const ResourceCacheInfo* aInfo, bool aSuccess);
|
||||
void OnResourceVerified(const ResourceCacheInfo* aInfo, bool aSuccess);
|
||||
|
||||
// To notify that either manifest or resource check is done.
|
||||
nsCOMPtr<nsIPackagedAppVerifierListener> mListener;
|
||||
|
||||
// The internal verification state.
|
||||
EState mState;
|
||||
|
||||
// Initialized as a normal origin. Will be updated once we verified the manifest.
|
||||
nsCString mPackageOrigin;
|
||||
|
||||
// The signature of the package.
|
||||
nsCString mSignature;
|
||||
|
||||
// Whether this package app is signed.
|
||||
bool mIsPackageSigned;
|
||||
|
||||
// The package cache entry (e.g. http://foo.com/app.pak) used to store
|
||||
// any necessarry signed package information.
|
||||
nsCOMPtr<nsICacheEntry> mPackageCacheEntry;
|
||||
|
||||
// The resource URI that we are computing its hash.
|
||||
nsCString mHashingResourceURI;
|
||||
|
||||
// Used to compute resource's hash value.
|
||||
nsCOMPtr<nsICryptoHash> mHasher;
|
||||
|
||||
// The last computed hash value for a resource. It will be set on every
|
||||
// |EndResourceHash| call.
|
||||
nsCString mLastComputedResourceHash;
|
||||
}; // class PackagedAppVerifier
|
||||
|
||||
} // namespace net
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_net_PackagedAppVerifier_h
|
@ -35,6 +35,7 @@ EXPORTS.mozilla.net += [
|
||||
'HttpInfo.h',
|
||||
'NullHttpChannel.h',
|
||||
'PackagedAppService.h',
|
||||
'PackagedAppVerifier.h',
|
||||
'PHttpChannelParams.h',
|
||||
'PSpdyPush.h',
|
||||
'TimingStruct.h',
|
||||
@ -80,6 +81,7 @@ UNIFIED_SOURCES += [
|
||||
'NullHttpChannel.cpp',
|
||||
'NullHttpTransaction.cpp',
|
||||
'PackagedAppService.cpp',
|
||||
'PackagedAppVerifier.cpp',
|
||||
'SpdyPush31.cpp',
|
||||
'SpdySession31.cpp',
|
||||
'SpdyStream31.cpp',
|
||||
|
@ -37,7 +37,8 @@ function Listener(callback) {
|
||||
Listener.prototype = {
|
||||
gotStartRequest: false,
|
||||
available: -1,
|
||||
gotStopRequest: false,
|
||||
gotStopRequestOK: false,
|
||||
gotFileNotFound: false,
|
||||
QueryInterface: function(iid) {
|
||||
if (iid.equals(Ci.nsISupports) ||
|
||||
iid.equals(Ci.nsIRequestObserver))
|
||||
@ -60,8 +61,8 @@ Listener.prototype = {
|
||||
this.gotStartRequest = true;
|
||||
},
|
||||
onStopRequest: function(request, ctx, status) {
|
||||
this.gotStopRequest = true;
|
||||
do_check_eq(status, 0);
|
||||
this.gotStopRequestOK = (Cr.NS_OK === status);
|
||||
this.gotFileNotFound = (Cr.NS_ERROR_FILE_NOT_FOUND === status);
|
||||
if (this._callback) {
|
||||
this._callback.call(null, this);
|
||||
}
|
||||
@ -71,6 +72,7 @@ Listener.prototype = {
|
||||
// The package content
|
||||
// getData formats it as described at http://www.w3.org/TR/web-packaging/#streamable-package-format
|
||||
var testData = {
|
||||
packageHeader: "manifest-signature: dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk\r\n",
|
||||
content: [
|
||||
{ headers: ["Content-Location: /index.html", "Content-Type: text/html"], data: "<html>\r\n <head>\r\n <script src=\"/scripts/app.js\"></script>\r\n ...\r\n </head>\r\n ...\r\n</html>\r\n", type: "text/html" },
|
||||
{ headers: ["Content-Location: /scripts/app.js", "Content-Type: text/javascript"], data: "module Math from '/scripts/helpers/math.js';\r\n...\r\n", type: "text/javascript" },
|
||||
@ -106,8 +108,16 @@ function regularContentHandler(metadata, response)
|
||||
response.bodyOutputStream.write(body, body.length);
|
||||
}
|
||||
|
||||
function contentHandlerWithSignature(metadata, response)
|
||||
{
|
||||
response.setHeader("Content-Type", 'application/package');
|
||||
var body = testData.packageHeader + testData.getData();
|
||||
response.bodyOutputStream.write(body, body.length);
|
||||
}
|
||||
|
||||
var httpserver = null;
|
||||
var originalPref = false;
|
||||
var originalDevMode = false;
|
||||
|
||||
function run_test()
|
||||
{
|
||||
@ -115,21 +125,50 @@ function run_test()
|
||||
httpserver = new HttpServer();
|
||||
httpserver.registerPathHandler("/package", contentHandler);
|
||||
httpserver.registerPathHandler("/regular", regularContentHandler);
|
||||
httpserver.registerPathHandler("/package_with_signature", contentHandlerWithSignature);
|
||||
httpserver.start(-1);
|
||||
|
||||
// Enable the feature and save the original pref value
|
||||
originalPref = Services.prefs.getBoolPref("network.http.enable-packaged-apps");
|
||||
originalDevMode = Services.prefs.getBoolPref("network.http.packaged-apps-developer-mode");
|
||||
Services.prefs.setBoolPref("network.http.enable-packaged-apps", true);
|
||||
Services.prefs.setBoolPref("network.http.packaged-apps-developer-mode", false);
|
||||
do_register_cleanup(reset_pref);
|
||||
|
||||
add_test(test_channel);
|
||||
add_test(test_channel_no_notificationCallbacks);
|
||||
add_test(test_channel_uris);
|
||||
|
||||
add_test(test_channel_with_signature);
|
||||
add_test(test_channel_with_signature_dev_mode);
|
||||
|
||||
// run tests
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
function test_channel_with_signature() {
|
||||
var channel = make_channel(uri+"/package_with_signature!//index.html");
|
||||
channel.notificationCallbacks = new LoadContextCallback(1024, false, false, false);
|
||||
channel.asyncOpen(new Listener(function(l) {
|
||||
// Since the manifest verification is not implemented yet, we should
|
||||
// get NS_ERROR_FILE_NOT_FOUND if the package has a signature while
|
||||
// not in developer mode.
|
||||
do_check_true(l.gotFileNotFound);
|
||||
run_next_test();
|
||||
}), null);
|
||||
}
|
||||
|
||||
function test_channel_with_signature_dev_mode() {
|
||||
Services.prefs.setBoolPref("network.http.packaged-apps-developer-mode", true);
|
||||
var channel = make_channel(uri+"/package_with_signature!//index.html");
|
||||
channel.notificationCallbacks = new LoadContextCallback(1024, false, false, false);
|
||||
channel.asyncOpen(new Listener(function(l) {
|
||||
do_check_true(l.gotStopRequestOK);
|
||||
Services.prefs.setBoolPref("network.http.packaged-apps-developer-mode", false);
|
||||
run_next_test();
|
||||
}), null);
|
||||
}
|
||||
|
||||
function test_channel(aNullNotificationCallbacks) {
|
||||
var channel = make_channel(uri+"/package!//index.html");
|
||||
|
||||
@ -141,7 +180,7 @@ function test_channel(aNullNotificationCallbacks) {
|
||||
// XXX: no content length available for this resource
|
||||
//do_check_true(channel.contentLength > 0);
|
||||
do_check_true(l.gotStartRequest);
|
||||
do_check_true(l.gotStopRequest);
|
||||
do_check_true(l.gotStopRequestOK);
|
||||
run_next_test();
|
||||
}), null);
|
||||
}
|
||||
@ -166,4 +205,5 @@ function check_regular_response(request, buffer) {
|
||||
function reset_pref() {
|
||||
// Set the pref to its original value
|
||||
Services.prefs.setBoolPref("network.http.enable-packaged-apps", originalPref);
|
||||
Services.prefs.setBoolPref("network.http.packaged-apps-developer-mode", originalDevMode);
|
||||
}
|
||||
|
221
netwerk/test/unit/test_packaged_app_verifier.js
Normal file
221
netwerk/test/unit/test_packaged_app_verifier.js
Normal file
@ -0,0 +1,221 @@
|
||||
//
|
||||
// This file tests the packaged app verifier - nsIPackagedAppVerifier
|
||||
//
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// All the test cases will ensure the callback order and args are exact the
|
||||
// same as how and what we feed into the verifier. We also check if verifier
|
||||
// gives the correct verification result like "is package signed", the
|
||||
// "package origin", etc.
|
||||
//
|
||||
// Note that the actual signature verification is not done yet. If we claim a
|
||||
// non-empty signature, the verifier will regard the verification as failed.
|
||||
// The actual verification process is addressed by Bug 1178518. Non-developer mode
|
||||
// test cases have to be modified once Bug 1178518 lands.
|
||||
//
|
||||
// We also test the developer mode here. In developer mode, no matter what kind
|
||||
// of signautre do we initialize the verifier, the package is always said signed.
|
||||
//
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
let gIoService = Cc["@mozilla.org/network/io-service;1"]
|
||||
.getService(Ci.nsIIOService);
|
||||
|
||||
let gPrefs = Cc["@mozilla.org/preferences-service;1"]
|
||||
.getService(Components.interfaces.nsIPrefBranch);
|
||||
|
||||
let gVerifier = Cc["@mozilla.org/network/packaged-app-verifier;1"]
|
||||
.createInstance(Ci.nsIPackagedAppVerifier);
|
||||
|
||||
let gCacheStorageService = Cc["@mozilla.org/netwerk/cache-storage-service;1"]
|
||||
.getService(Ci.nsICacheStorageService);;
|
||||
|
||||
let gLoadContextInfoFactory =
|
||||
Cu.import("resource://gre/modules/LoadContextInfo.jsm", {}).LoadContextInfo;
|
||||
|
||||
const kUriIdx = 0;
|
||||
const kStatusCodeIdx = 1;
|
||||
const kVerificationSuccessIdx = 2;
|
||||
|
||||
function enable_developer_mode()
|
||||
{
|
||||
gPrefs.setBoolPref("network.http.packaged-apps-developer-mode", true);
|
||||
}
|
||||
|
||||
function reset_developer_mode()
|
||||
{
|
||||
gPrefs.clearUserPref("network.http.packaged-apps-developer-mode");
|
||||
}
|
||||
|
||||
function createVerifierListener(aExpecetedCallbacks,
|
||||
aExpectedOrigin,
|
||||
aExpectedIsSigned,
|
||||
aPackageCacheEntry) {
|
||||
let cnt = 0;
|
||||
|
||||
return {
|
||||
onVerified: function(aIsManifest,
|
||||
aUri,
|
||||
aCacheEntry,
|
||||
aStatusCode,
|
||||
aIsLastPart,
|
||||
aVerificationSuccess) {
|
||||
cnt++;
|
||||
|
||||
let expectedCallback = aExpecetedCallbacks[cnt - 1];
|
||||
let isManifest = (cnt === 1);
|
||||
let isLastPart = (cnt === aExpecetedCallbacks.length);
|
||||
|
||||
// Check if we are called back with correct info.
|
||||
equal(aIsManifest, isManifest, 'is manifest');
|
||||
equal(aUri.asciiSpec, expectedCallback[kUriIdx], 'URL');
|
||||
equal(aStatusCode, expectedCallback[kStatusCodeIdx], 'status code');
|
||||
equal(aIsLastPart, isLastPart, 'is lastPart');
|
||||
equal(aVerificationSuccess, expectedCallback[kVerificationSuccessIdx], 'verification result');
|
||||
|
||||
if (isManifest) {
|
||||
// Check if the verifier got the right package info.
|
||||
equal(gVerifier.packageOrigin, aExpectedOrigin, 'package origin');
|
||||
equal(gVerifier.isPackageSigned, aExpectedIsSigned, 'is package signed');
|
||||
|
||||
// Check if the verifier wrote the signed package origin to the cache.
|
||||
ok(!!aPackageCacheEntry, aPackageCacheEntry.key);
|
||||
let signePakOriginInCache = aPackageCacheEntry.getMetaDataElement('signed-pak-origin');
|
||||
equal(signePakOriginInCache,
|
||||
(aExpectedIsSigned ? aExpectedOrigin : ''),
|
||||
'signed-pak-origin in cache');
|
||||
}
|
||||
|
||||
if (isLastPart) {
|
||||
reset_developer_mode();
|
||||
run_next_test();
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
function feedResources(aExpectedCallbacks) {
|
||||
for (let i = 0; i < aExpectedCallbacks.length; i++) {
|
||||
let expectedCallback = aExpectedCallbacks[i];
|
||||
let isLastPart = (i === aExpectedCallbacks.length - 1);
|
||||
|
||||
let uri = gIoService.newURI(expectedCallback[kUriIdx], null, null);
|
||||
gVerifier.onStartRequest(null, uri);
|
||||
|
||||
let info = gVerifier.createResourceCacheInfo(uri,
|
||||
null,
|
||||
expectedCallback[kStatusCodeIdx],
|
||||
isLastPart);
|
||||
|
||||
gVerifier.onStopRequest(null, info, expectedCallback[kStatusCodeIdx]);
|
||||
}
|
||||
}
|
||||
|
||||
function createPackageCache(aPackageUriAsAscii, aLoadContextInfo) {
|
||||
let cacheStorage =
|
||||
gCacheStorageService.memoryCacheStorage(aLoadContextInfo);
|
||||
|
||||
let uri = gIoService.newURI(aPackageUriAsAscii, null, null);
|
||||
return cacheStorage.openTruncate(uri, '');
|
||||
}
|
||||
|
||||
function test_no_signature(aDeveloperMode) {
|
||||
const kOrigin = 'http://foo.com';
|
||||
|
||||
aDeveloperMode = !!aDeveloperMode;
|
||||
|
||||
// If the package has no signature and not in developer mode, the package is unsigned
|
||||
// but the verification result is always true.
|
||||
|
||||
const expectedCallbacks = [
|
||||
// URL statusCode verificationResult
|
||||
[kOrigin + '/manifest', Cr.NS_OK, true],
|
||||
[kOrigin + '/1.html', Cr.NS_OK, true],
|
||||
[kOrigin + '/2.js', Cr.NS_OK, true],
|
||||
[kOrigin + '/3.jpg', Cr.NS_OK, true],
|
||||
[kOrigin + '/4.html', Cr.NS_OK, true],
|
||||
[kOrigin + '/5.css', Cr.NS_OK, true],
|
||||
];
|
||||
|
||||
let isPackageSigned = false;
|
||||
|
||||
// We only require the package URL to be different in each test case.
|
||||
let packageUriString = kOrigin + '/pak' + (aDeveloperMode ? '-dev' : '');
|
||||
|
||||
let packageCacheEntry =
|
||||
createPackageCache(packageUriString, gLoadContextInfoFactory.default);
|
||||
|
||||
let verifierListener = createVerifierListener(expectedCallbacks,
|
||||
kOrigin,
|
||||
isPackageSigned,
|
||||
packageCacheEntry);
|
||||
|
||||
gVerifier.init(verifierListener, kOrigin, '', packageCacheEntry);
|
||||
|
||||
feedResources(expectedCallbacks);
|
||||
}
|
||||
|
||||
function test_invalid_signature(aDeveloperMode) {
|
||||
const kOrigin = 'http://bar.com';
|
||||
|
||||
aDeveloperMode = !!aDeveloperMode;
|
||||
|
||||
// Since we haven't implemented signature verification, the verification always
|
||||
// fails if the signature exists.
|
||||
|
||||
let verificationResult = aDeveloperMode; // Verification always success in developer mode.
|
||||
let isPackageSigned = aDeveloperMode; // Package is always considered as signed in developer mode.
|
||||
|
||||
const expectedCallbacks = [
|
||||
// URL statusCode verificationResult
|
||||
[kOrigin + '/manifest', Cr.NS_OK, verificationResult],
|
||||
[kOrigin + '/1.html', Cr.NS_OK, verificationResult],
|
||||
[kOrigin + '/2.js', Cr.NS_OK, verificationResult],
|
||||
[kOrigin + '/3.jpg', Cr.NS_OK, verificationResult],
|
||||
[kOrigin + '/4.html', Cr.NS_OK, verificationResult],
|
||||
[kOrigin + '/5.css', Cr.NS_OK, verificationResult],
|
||||
];
|
||||
|
||||
let packageUriString = kOrigin + '/pak' + (aDeveloperMode ? '-dev' : '');
|
||||
let packageCacheEntry =
|
||||
createPackageCache(packageUriString, gLoadContextInfoFactory.private);
|
||||
|
||||
let verifierListener = createVerifierListener(expectedCallbacks,
|
||||
kOrigin,
|
||||
isPackageSigned,
|
||||
packageCacheEntry);
|
||||
|
||||
gVerifier.init(verifierListener, kOrigin, 'invalid signature', packageCacheEntry);
|
||||
|
||||
feedResources(expectedCallbacks);
|
||||
}
|
||||
|
||||
function test_no_signature_developer_mode()
|
||||
{
|
||||
enable_developer_mode()
|
||||
test_no_signature(true);
|
||||
}
|
||||
|
||||
function test_invalid_signature_developer_mode()
|
||||
{
|
||||
enable_developer_mode()
|
||||
test_invalid_signature(true);
|
||||
}
|
||||
|
||||
function run_test()
|
||||
{
|
||||
ok(!!gVerifier);
|
||||
|
||||
// Test cases in non-developer mode.
|
||||
add_test(test_no_signature);
|
||||
add_test(test_invalid_signature);
|
||||
|
||||
// Test cases in developer mode.
|
||||
add_test(test_no_signature_developer_mode);
|
||||
add_test(test_invalid_signature_developer_mode);
|
||||
|
||||
// run tests
|
||||
run_next_test();
|
||||
}
|
@ -322,6 +322,7 @@ skip-if = os == "android"
|
||||
[test_multipart_streamconv_application_package.js]
|
||||
[test_safeoutputstream_append.js]
|
||||
[test_packaged_app_service.js]
|
||||
[test_packaged_app_verifier.js]
|
||||
[test_suspend_channel_before_connect.js]
|
||||
[test_inhibit_caching.js]
|
||||
[test_dns_disable_ipv4.js]
|
||||
|
Loading…
Reference in New Issue
Block a user