Bug 1178525 - Introduce PackagedAppVerifier and use it to control the timing we serve packaged content. r=valentin.
authorHenry Chang <hchang@mozilla.com>
Mon, 07 Sep 2015 19:12:02 +0800
changeset 293883 b890e7641b133f42b5b8de04dee260b025557663
parent 293882 f1b54a9c9f9b645a86099c17d3c14e6f5a9f78ba
child 293884 afc2ca41d78b41c0126efa5116fc1bce690c4365
push id5245
push userraliiev@mozilla.com
push dateThu, 29 Oct 2015 11:30:51 +0000
treeherdermozilla-beta@dac831dc1bd0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersvalentin
bugs1178525
milestone43.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1178525 - Introduce PackagedAppVerifier and use it to control the timing we serve packaged content. r=valentin.
modules/libpref/init/all.js
netwerk/base/moz.build
netwerk/base/nsIPackagedAppVerifier.idl
netwerk/build/nsNetCID.h
netwerk/build/nsNetModule.cpp
netwerk/protocol/http/PackagedAppService.cpp
netwerk/protocol/http/PackagedAppService.h
netwerk/protocol/http/PackagedAppVerifier.cpp
netwerk/protocol/http/PackagedAppVerifier.h
netwerk/protocol/http/moz.build
netwerk/test/unit/test_packaged_app_channel.js
netwerk/test/unit/test_packaged_app_verifier.js
netwerk/test/unit/xpcshell.ini
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -1430,16 +1430,21 @@ pref("network.http.enforce-framing.http1
 pref("network.http.enforce-framing.soft", true);
 
 // Whether nsHttpChannel should use the PackagedAppService to load
 // resources from a package when directed to a URL
 // such as http://domain.com/package.pak!//resource.html
 // 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)
 // per Section 4.7 "Low-Latency Data Service Class".
 pref("network.ftp.data.qos", 0);
 pref("network.ftp.control.qos", 0);
 
 // If this pref is false only one xpcom event will be served per poll
--- a/netwerk/base/moz.build
+++ b/netwerk/base/moz.build
@@ -64,16 +64,17 @@ XPIDL_SOURCES += [
     'nsINetworkLinkService.idl',
     'nsINetworkPredictor.idl',
     'nsINetworkPredictorVerifier.idl',
     'nsINetworkProperties.idl',
     'nsINSSErrorsService.idl',
     'nsINullChannel.idl',
     'nsIPACGenerator.idl',
     'nsIPackagedAppService.idl',
+    'nsIPackagedAppVerifier.idl',
     'nsIParentChannel.idl',
     'nsIParentRedirectingChannel.idl',
     'nsIPermission.idl',
     'nsIPermissionManager.idl',
     'nsIPrivateBrowsingChannel.idl',
     'nsIProgressEventSink.idl',
     'nsIPrompt.idl',
     'nsIProtocolHandler.idl',
new file mode 100644
--- /dev/null
+++ b/netwerk/base/nsIPackagedAppVerifier.idl
@@ -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);
+};
+
--- a/netwerk/build/nsNetCID.h
+++ b/netwerk/build/nsNetCID.h
@@ -901,16 +901,25 @@
 #define NS_PACKAGEDAPPSERVICE_CID                      \
 {   /* adef6762-41b9-4470-a06a-dc29cf8de381 */         \
     0xadef6762,                                        \
     0x41b9,                                            \
     0x4470,                                            \
   { 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
  */
 
 // service implementing nsICookieManager and nsICookieManager2.
 #define NS_COOKIEMANAGER_CONTRACTID \
     "@mozilla.org/cookiemanager;1"
--- a/netwerk/build/nsNetModule.cpp
+++ b/netwerk/build/nsNetModule.cpp
@@ -259,20 +259,22 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsHttpAct
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsHttpBasicAuth)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsHttpDigestAuth)
 } // namespace net
 } // namespace mozilla
 #endif // !NECKO_PROTOCOL_http
 
 #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"
 
 #ifdef NECKO_PROTOCOL_res
 // resource
 #include "nsResProtocolHandler.h"
 #include "ExtensionProtocolHandler.h"
@@ -727,16 +729,17 @@ NS_DEFINE_NAMED_CID(NS_AUTHURLPARSER_CID
 NS_DEFINE_NAMED_CID(NS_STANDARDURL_CID);
 NS_DEFINE_NAMED_CID(NS_ARRAYBUFFERINPUTSTREAM_CID);
 NS_DEFINE_NAMED_CID(NS_BUFFEREDINPUTSTREAM_CID);
 NS_DEFINE_NAMED_CID(NS_BUFFEREDOUTPUTSTREAM_CID);
 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);
 #endif
 NS_DEFINE_NAMED_CID(NS_NSINDEXEDTOHTMLCONVERTER_CID);
 NS_DEFINE_NAMED_CID(NS_DIRINDEXPARSER_CID);
 NS_DEFINE_NAMED_CID(NS_MULTIMIXEDCONVERTER_CID);
 NS_DEFINE_NAMED_CID(NS_UNKNOWNDECODER_CID);
@@ -875,16 +878,17 @@ static const mozilla::Module::CIDEntry k
     { &kNS_STANDARDURL_CID, false, nullptr, nsStandardURLConstructor },
     { &kNS_ARRAYBUFFERINPUTSTREAM_CID, false, nullptr, ArrayBufferInputStreamConstructor },
     { &kNS_BUFFEREDINPUTSTREAM_CID, false, nullptr, nsBufferedInputStream::Create },
     { &kNS_BUFFEREDOUTPUTSTREAM_CID, false, nullptr, nsBufferedOutputStream::Create },
     { &kNS_MIMEINPUTSTREAM_CID, false, nullptr, nsMIMEInputStreamConstructor },
     { &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 },
 #endif
     { &kNS_NSINDEXEDTOHTMLCONVERTER_CID, false, nullptr, nsIndexedToHTML::Create },
     { &kNS_DIRINDEXPARSER_CID, false, nullptr, nsDirIndexParserConstructor },
     { &kNS_MULTIMIXEDCONVERTER_CID, false, nullptr, CreateNewMultiMixedConvFactory },
     { &kNS_UNKNOWNDECODER_CID, false, nullptr, CreateNewUnknownDecoderFactory },
@@ -1025,16 +1029,17 @@ static const mozilla::Module::ContractID
     { NS_STANDARDURL_CONTRACTID, &kNS_STANDARDURL_CID },
     { NS_ARRAYBUFFERINPUTSTREAM_CONTRACTID, &kNS_ARRAYBUFFERINPUTSTREAM_CID },
     { NS_BUFFEREDINPUTSTREAM_CONTRACTID, &kNS_BUFFEREDINPUTSTREAM_CID },
     { NS_BUFFEREDOUTPUTSTREAM_CONTRACTID, &kNS_BUFFEREDOUTPUTSTREAM_CID },
     { NS_MIMEINPUTSTREAM_CONTRACTID, &kNS_MIMEINPUTSTREAM_CID },
     { 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 },
 #endif
     { NS_ISTREAMCONVERTER_KEY INDEX_TO_HTML, &kNS_NSINDEXEDTOHTMLCONVERTER_CID },
     { NS_DIRINDEXPARSER_CONTRACTID, &kNS_DIRINDEXPARSER_CID },
     { NS_ISTREAMCONVERTER_KEY MULTI_MIXED_X, &kNS_MULTIMIXEDCONVERTER_CID },
     { NS_ISTREAMCONVERTER_KEY MULTI_BYTERANGES, &kNS_MULTIMIXEDCONVERTER_CID },
--- a/netwerk/protocol/http/PackagedAppService.cpp
+++ b/netwerk/protocol/http/PackagedAppService.cpp
@@ -164,16 +164,85 @@ HeaderCopier::ShouldCopy(const nsACStrin
     if (header == kHeadersCopyBlacklist[i]) {
       return false;
     }
   }
 
   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
 PackagedAppService::CacheEntryWriter::CopyHeadersFromChannel(nsIChannel *aChannel,
                                                              nsHttpResponseHead *aHead)
 {
   if (!aChannel || !aHead) {
     return NS_ERROR_INVALID_ARG;
@@ -184,27 +253,22 @@ PackagedAppService::CacheEntryWriter::Co
     return NS_ERROR_FAILURE;
   }
 
   nsRefPtr<HeaderCopier> headerCopier = new HeaderCopier(aHead);
   return httpChan->VisitResponseHeaders(headerCopier);
 }
 
 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
 PackagedAppService::CacheEntryWriter::OnStartRequest(nsIRequest *aRequest,
                                                      nsISupports *aContext)
 {
   nsresult rv;
   nsCOMPtr<nsIResponseHeadProvider> provider(do_QueryInterface(aRequest));
@@ -270,22 +334,18 @@ PackagedAppService::CacheEntryWriter::On
 
 NS_IMETHODIMP
 PackagedAppService::CacheEntryWriter::OnDataAvailable(nsIRequest *aRequest,
                                                       nsISupports *aContext,
                                                       nsIInputStream *aInputStream,
                                                       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;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 
 NS_IMPL_ISUPPORTS(PackagedAppService::PackagedAppChannelListener, nsIStreamListener)
 
 NS_IMETHODIMP
 PackagedAppService::PackagedAppChannelListener::OnStartRequest(nsIRequest *aRequest,
@@ -295,16 +355,36 @@ PackagedAppService::PackagedAppChannelLi
   nsCOMPtr<nsICacheInfoChannel> cacheChan = do_QueryInterface(aRequest);
   if (cacheChan) {
       cacheChan->IsFromCache(&isFromCache);
   }
 
   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);
 }
 
 NS_IMETHODIMP
 PackagedAppService::PackagedAppChannelListener::OnStopRequest(nsIRequest *aRequest,
                                                     nsISupports *aContext,
@@ -321,39 +401,63 @@ PackagedAppService::PackagedAppChannelLi
                                                       uint32_t aCount)
 {
   return mListener->OnDataAvailable(aRequest, aContext, aInputStream, aOffset, aCount);
 }
 
 
 ////////////////////////////////////////////////////////////////////////////////
 
-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 =
     do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   rv = cacheStorageService->DiskCacheStorage(aInfo, false,
                                              getter_AddRefs(mCacheStorage));
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   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)
 {
   // In case an error occurs in this method mWriter should be null
   // so we don't accidentally write to the previous resource's cache entry.
   mWriter = nullptr;
 
@@ -369,17 +473,28 @@ PackagedAppService::PackagedAppDownloade
   rv = CacheEntryWriter::Create(uri, mCacheStorage, getter_AddRefs(mWriter));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return NS_OK;
   }
 
   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
 PackagedAppService::PackagedAppDownloader::GetSubresourceURI(nsIRequest * aRequest,
                                                              nsIURI ** aResult)
 {
   nsresult rv;
   nsCOMPtr<nsIResponseHeadProvider> provider(do_QueryInterface(aRequest));
@@ -437,89 +552,162 @@ PackagedAppService::PackagedAppDownloade
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   partURI.forget(aResult);
   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,
                                                          nsresult aStatusCode)
 {
   nsCOMPtr<nsIMultiPartChannel> multiChannel(do_QueryInterface(aRequest));
   nsresult rv;
 
   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;
+  }
+
+  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;
   }
 
-  // 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;
+  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;
   }
 
-  nsRefPtr<PackagedAppDownloader> kungFuDeathGrip(this);
-  // NotifyPackageDownloaded removes the ref from the array. Keep a temp ref
-  if (gPackagedAppService) {
-    gPackagedAppService->NotifyPackageDownloaded(mPackageKey);
+  PackagedAppDownloader *self = static_cast<PackagedAppDownloader*>(aClosure);
+
+  if (!self->mWriter) {
+    *aWriteCount = aCount;
+    return NS_OK;
   }
-  ClearCallbacks(aStatusCode);
-  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,
                                                            nsIInputStream *aInputStream,
                                                            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
 PackagedAppService::PackagedAppDownloader::AddCallback(nsIURI *aURI,
                                                        nsICacheEntryOpenCallback *aCallback)
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread(), "mCallbacks hashtable is not thread safe");
   nsAutoCString spec;
@@ -531,16 +719,22 @@ PackagedAppService::PackagedAppDownloade
   // Check if we already have a resource waiting for this resource
   nsCOMArray<nsICacheEntryOpenCallback>* array = mCallbacks.Get(spec);
   if (array) {
     if (array->Length() == 0) {
       // The download of this resource has already completed, hence we don't
       // 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 {
       LOG(("[%p]    > adding to array\n", this));
       // Add this resource to the callback array
       array->AppendObject(aCallback);
     }
   } else {
@@ -567,28 +761,29 @@ PackagedAppService::PackagedAppDownloade
   LogURI("PackagedAppService::PackagedAppDownloader::CallCallbacks", this, aURI);
   LOG(("[%p]    > status:%X\n", this, aResult));
 
   nsAutoCString spec;
   aURI->GetAsciiSpec(spec);
 
   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));
       // We call to AsyncOpenURI which automatically calls the callback.
       mCacheStorage->AsyncOpenURI(aURI, EmptyCString(),
                                   nsICacheStorage::OPEN_READONLY, callback);
     }
     // Clear the array but leave it in the hashtable
     // 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
     // downloading the package, we can simply return it from the cache.
     nsCOMArray<nsICacheEntryOpenCallback>* newArray =
       new nsCOMArray<nsICacheEntryOpenCallback>();
     mCallbacks.Put(spec, newArray);
     LOG(("[%p]    > created array\n", this));
@@ -600,16 +795,18 @@ PackagedAppService::PackagedAppDownloade
 
 nsresult
 PackagedAppService::PackagedAppDownloader::ClearCallbacks(nsresult aResult)
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread(), "mCallbacks hashtable is not thread safe");
   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();
 
     if (NS_SUCCEEDED(aResult)) {
       // For success conditions we try to open the cache entry.
       // This can occur when the package metadata is served from the cache,
       // as it hasn't changed, but the entries are still in the cache.
@@ -635,16 +832,94 @@ PackagedAppService::PackagedAppDownloade
 
     // Finally, we remove this entry from the hashtable.
     iter.Remove();
   }
 
   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()
 {
   gPackagedAppService = this;
   gPASLog = PR_NewLogModule("PackagedAppService");
   LOG(("[%p] Created PackagedAppService\n", this));
 }
@@ -781,17 +1056,19 @@ PackagedAppService::GetResource(nsIChann
   if (cacheChan) {
     // Each resource in the package will be put in its own cache entry
     // during the first load of the package, so we only want the channel to
     // cache the response head, not the entire content of the package.
     cacheChan->SetCacheOnlyMetadata(true);
   }
 
   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;
   }
 
   downloader->AddCallback(uri, aCallback);
 
   nsCOMPtr<nsIStreamConverterService> streamconv =
     do_GetService("@mozilla.org/streamConverters;1", &rv);
--- a/netwerk/protocol/http/PackagedAppService.h
+++ b/netwerk/protocol/http/PackagedAppService.h
@@ -5,16 +5,19 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_net_PackagedAppService_h
 #define mozilla_net_PackagedAppService_h
 
 #include "nsIPackagedAppService.h"
 #include "nsILoadContextInfo.h"
 #include "nsICacheStorage.h"
+#include "PackagedAppVerifier.h"
+#include "nsIMultiPartChannel.h"
+#include "PackagedAppVerifier.h"
 
 namespace mozilla {
 namespace net {
 
 class nsHttpResponseHead;
 
 // This service is used to download packages from the web.
 // Individual resources in the package are saved in the browser cache. It also
@@ -56,70 +59,126 @@ private:
     NS_DECL_NSIREQUESTOBSERVER
 
     // If successful, calling this static method will create a new
     // CacheEntryWriter and will create the cache entry associated to the
     // resource.
     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() { }
 
     // Copy the security-info metadata from the channel to the cache entry
     // so packaged resources can be accessed over https.
     nsresult CopySecurityInfo(nsIChannel *aChannel);
 
     // Copy headers that apply to all resources in the package
     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;
   };
 
   // This class is used to download a packaged app. It acts as a listener
   // for the nsMultiMixedConv object that parses the package.
   // There is an OnStartRequest, OnDataAvailable*, OnStopRequest sequence called
   // for each resource
   // The PackagedAppService holds a hash-table of the PackagedAppDownloaders
   // that are in progress to coalesce same loads.
   // Once the downloading is completed, it should call
   // 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
     nsresult CallCallbacks(nsIURI *aURI, nsICacheEntry *aEntry, nsresult aResult);
     // Clears all the callbacks for this package
     // This would get called at the end of downloading the package and would
     // cause us to call OnCacheEntryAvailable with a null entry. This would be
     // equivalent to a 404 when loading from the net.
@@ -139,16 +198,24 @@ private:
     // Should only be used on the main thread.
     nsClassHashtable<nsCStringHashKey, nsCOMArray<nsICacheEntryOpenCallback>> mCallbacks;
     // The key with which this package is inserted in
     // PackagedAppService::mDownloadingPackages
     nsCString mPackageKey;
 
     // 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
   // and forwards them to the listener.
   // The target is a `mListener` which converts the package to individual
   // resources and serves them to mDownloader.
   // This class is able to perform conditional actions based on whether the
   // underlying nsIHttpChannel is served from the cache.
new file mode 100644
--- /dev/null
+++ b/netwerk/protocol/http/PackagedAppVerifier.cpp
@@ -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
new file mode 100644
--- /dev/null
+++ b/netwerk/protocol/http/PackagedAppVerifier.h
@@ -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
--- a/netwerk/protocol/http/moz.build
+++ b/netwerk/protocol/http/moz.build
@@ -30,16 +30,17 @@ EXPORTS += [
 
 EXPORTS.mozilla.net += [
     'HttpBaseChannel.h',
     'HttpChannelChild.h',
     'HttpChannelParent.h',
     'HttpInfo.h',
     'NullHttpChannel.h',
     'PackagedAppService.h',
+    'PackagedAppVerifier.h',
     'PHttpChannelParams.h',
     'PSpdyPush.h',
     'TimingStruct.h',
 ]
 
 # ASpdySession.cpp and nsHttpAuthCache cannot be built in unified mode because
 # they use plarena.h.
 SOURCES += [
@@ -75,16 +76,17 @@ UNIFIED_SOURCES += [
     'nsHttpNTLMAuth.cpp',
     'nsHttpPipeline.cpp',
     'nsHttpRequestHead.cpp',
     'nsHttpResponseHead.cpp',
     'nsHttpTransaction.cpp',
     'NullHttpChannel.cpp',
     'NullHttpTransaction.cpp',
     'PackagedAppService.cpp',
+    'PackagedAppVerifier.cpp',
     'SpdyPush31.cpp',
     'SpdySession31.cpp',
     'SpdyStream31.cpp',
     'SpdyZlibReporter.cpp',
     'TunnelUtils.cpp',
 ]
 
 # These files cannot be built in unified mode because of OS X headers.
--- a/netwerk/test/unit/test_packaged_app_channel.js
+++ b/netwerk/test/unit/test_packaged_app_channel.js
@@ -32,17 +32,18 @@ function make_channel(url) {
 
 function Listener(callback) {
     this._callback = 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))
             return this;
         throw Cr.NS_ERROR_NO_INTERFACE;
     },
     onDataAvailable: function(request, ctx, stream, offset, count) {
         try {
@@ -55,27 +56,28 @@ Listener.prototype = {
         catch (ex) {
             do_throw(ex);
         }
     },
     onStartRequest: function(request, ctx) {
         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);
         }
     }
 };
 
 // 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" },
    { headers: ["Content-Location: /scripts/helpers/math.js", "Content-Type: text/javascript"], data: "export function sum(nums) { ... }\r\n...\r\n", type: "text/javascript" }
   ],
   token : "gc0pJq0M:08jU534c0p",
   getData: function() {
     var str = "";
@@ -101,52 +103,89 @@ function contentHandler(metadata, respon
 }
 
 function regularContentHandler(metadata, response)
 {
   var body = "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()
 {
   // setup 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");
 
   if (!aNullNotificationCallbacks) {
     channel.notificationCallbacks = new LoadContextCallback(1024, false, false, false);
   }
 
   channel.asyncOpen(new Listener(function(l) {
     // 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);
 }
 
 function test_channel_no_notificationCallbacks() {
   test_channel(true);
 }
 
@@ -161,9 +200,10 @@ function check_regular_response(request,
   do_check_eq(request.responseStatus, 200);
   do_check_eq(buffer, "response");
   run_next_test();
 }
 
 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);
 }
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_packaged_app_verifier.js
@@ -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();
+}
--- a/netwerk/test/unit/xpcshell.ini
+++ b/netwerk/test/unit/xpcshell.ini
@@ -317,14 +317,15 @@ skip-if = os != "win"
 [test_websocket_offline.js]
 [test_tls_server.js]
 # The local cert service used by this test is not currently shipped on Android
 skip-if = os == "android"
 [test_1073747.js]
 [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]
 [test_dns_disable_ipv6.js]
 [test_packaged_app_service_paths.js]
 [test_bug1195415.js]