Bug 1178525 - Introduce PackagedAppVerifier and use it to control the timing we serve packaged content. r=valentin.

This commit is contained in:
Henry Chang 2015-09-07 19:12:02 +08:00
parent 34122d2ae8
commit d0d222b8de
13 changed files with 1244 additions and 72 deletions

View File

@ -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)

View File

@ -69,6 +69,7 @@ XPIDL_SOURCES += [
'nsINullChannel.idl',
'nsIPACGenerator.idl',
'nsIPackagedAppService.idl',
'nsIPackagedAppVerifier.idl',
'nsIParentChannel.idl',
'nsIParentRedirectingChannel.idl',
'nsIPermission.idl',

View 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);
};

View File

@ -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

View File

@ -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 },

View File

@ -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;
}

View File

@ -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

View 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

View 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

View File

@ -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',

View File

@ -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);
}

View 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();
}

View File

@ -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]