Bug 1263793 - Using content signature verifier for verifying remote newtab, r=keeler,mayhemer
☠☠ backed out by 7f816e3b56be ☠ ☠
authorFranziskus Kiefer <franziskuskiefer@gmail.com>
Thu, 19 May 2016 10:59:48 +0200
changeset 305858 21d8bb5af7b4619ee5c594fa57797aa94f2ac910
parent 305857 234f9780a4cd5caa3f09cd4d0a7dbcf507d478cd
child 305859 aa1eab6436badfd108056b392c06ee1c95e533dc
push id79684
push userfranziskuskiefer@gmail.com
push dateWed, 20 Jul 2016 15:34:57 +0000
treeherdermozilla-inbound@21d8bb5af7b4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskeeler, mayhemer
bugs1263793
milestone50.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 1263793 - Using content signature verifier for verifying remote newtab, r=keeler,mayhemer MozReview-Commit-ID: CHUPgBr8WaC
dom/security/ContentVerifier.cpp
dom/security/ContentVerifier.h
dom/security/nsCSPUtils.cpp
dom/security/test/contentverifier/browser.ini
dom/security/test/contentverifier/browser_verify_content_about_newtab.js
dom/security/test/contentverifier/browser_verify_content_about_newtab2.js
dom/security/test/contentverifier/file_about_newtab_bad_csp_signature
dom/security/test/contentverifier/file_about_newtab_good_signature
dom/security/test/contentverifier/file_about_newtab_sri_signature
dom/security/test/contentverifier/file_contentserver.sjs
dom/security/test/contentverifier/goodChain.pem
dom/security/test/contentverifier/head.js
netwerk/protocol/http/nsHttpChannel.cpp
security/manager/ssl/ContentSignatureVerifier.cpp
security/manager/ssl/ContentSignatureVerifier.h
security/manager/ssl/nsIContentSignatureVerifier.idl
--- a/dom/security/ContentVerifier.cpp
+++ b/dom/security/ContentVerifier.cpp
@@ -2,374 +2,225 @@
 /* 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 "ContentVerifier.h"
 
 #include "mozilla/fallible.h"
 #include "mozilla/Logging.h"
-#include "mozilla/Preferences.h"
-#include "mozilla/StaticPtr.h"
-#include "nsCharSeparatedTokenizer.h"
+#include "MainThreadUtils.h"
 #include "nsIInputStream.h"
 #include "nsIRequest.h"
-#include "nssb64.h"
-#include "nsSecurityHeaderParser.h"
 #include "nsServiceManagerUtils.h"
 #include "nsStringStream.h"
-#include "nsThreadUtils.h"
 
 using namespace mozilla;
 
 static LazyLogModule gContentVerifierPRLog("ContentVerifier");
 #define CSV_LOG(args) MOZ_LOG(gContentVerifierPRLog, LogLevel::Debug, args)
 
-// Content-Signature prefix
-const nsLiteralCString kPREFIX = NS_LITERAL_CSTRING("Content-Signature:\x00");
-
-NS_IMPL_ISUPPORTS(ContentVerifier, nsIStreamListener, nsISupports);
+NS_IMPL_ISUPPORTS(ContentVerifier,
+                  nsIContentSignatureReceiverCallback,
+                  nsIStreamListener);
 
 nsresult
-ContentVerifier::Init(const nsAString& aContentSignatureHeader)
+ContentVerifier::Init(const nsACString& aContentSignatureHeader,
+                      nsIRequest* aRequest, nsISupports* aContext)
 {
-  mVks = Preferences::GetString("browser.newtabpage.remote.keys");
-
-  if (aContentSignatureHeader.IsEmpty() || mVks.IsEmpty()) {
-    CSV_LOG(
-      ("Content-Signature header and verification keys must not be empty!\n"));
+  MOZ_ASSERT(NS_IsMainThread());
+  if (aContentSignatureHeader.IsEmpty()) {
+    CSV_LOG(("Content-Signature header must not be empty!\n"));
     return NS_ERROR_INVALID_SIGNATURE;
   }
 
-  nsresult rv = ParseContentSignatureHeader(aContentSignatureHeader);
-  NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_SIGNATURE);
-  return CreateContext();
+  // initialise the content signature "service"
+  nsresult rv;
+  mVerifier =
+    do_CreateInstance("@mozilla.org/security/contentsignatureverifier;1", &rv);
+  if (NS_FAILED(rv) || !mVerifier) {
+    return NS_ERROR_INVALID_SIGNATURE;
+  }
+
+  // Keep references to the request and context. We need them in FinishSignature
+  // and the ContextCreated callback.
+  mContentRequest = aRequest;
+  mContentContext = aContext;
+
+  return mVerifier->CreateContextWithoutCertChain(
+    this, aContentSignatureHeader,
+    NS_LITERAL_CSTRING("remote-newtab-signer.mozilla.org"));
 }
 
 /**
  * Implement nsIStreamListener
  * We buffer the entire content here and kick off verification
  */
 NS_METHOD
 AppendNextSegment(nsIInputStream* aInputStream, void* aClosure,
                   const char* aRawSegment, uint32_t aToOffset, uint32_t aCount,
                   uint32_t* outWrittenCount)
 {
   FallibleTArray<nsCString>* decodedData =
     static_cast<FallibleTArray<nsCString>*>(aClosure);
-  nsAutoCString segment(aRawSegment, aCount);
+  nsDependentCSubstring segment(aRawSegment, aCount);
   if (!decodedData->AppendElement(segment, fallible)) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
   *outWrittenCount = aCount;
   return NS_OK;
 }
 
+void
+ContentVerifier::FinishSignature()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  nsCOMPtr<nsIStreamListener> nextListener;
+  nextListener.swap(mNextListener);
+
+  // Verify the content:
+  // If this fails, we return an invalid signature error to load a fallback page.
+  // If everthing is good, we return a new stream to the next listener and kick
+  // that one off.
+  bool verified = false;
+  nsresult rv = NS_OK;
+
+  // If the content signature check fails, stop the load
+  // and return a signature error. NSS resources are freed by the
+  // ContentSignatureVerifier on destruction.
+  if (NS_FAILED(mVerifier->End(&verified)) || !verified) {
+    CSV_LOG(("failed to verify content\n"));
+    (void)nextListener->OnStopRequest(mContentRequest, mContentContext,
+                                      NS_ERROR_INVALID_SIGNATURE);
+    return;
+  }
+  CSV_LOG(("Successfully verified content signature.\n"));
+
+  // We emptied the input stream so we have to create a new one from mContent
+  // to hand it to the consuming listener.
+  uint64_t offset = 0;
+  for (uint32_t i = 0; i < mContent.Length(); ++i) {
+    nsCOMPtr<nsIInputStream> oInStr;
+    rv = NS_NewCStringInputStream(getter_AddRefs(oInStr), mContent[i]);
+    if (NS_FAILED(rv)) {
+      break;
+    }
+    // let the next listener know that there is data in oInStr
+    rv = nextListener->OnDataAvailable(mContentRequest, mContentContext, oInStr,
+                                       offset, mContent[i].Length());
+    offset += mContent[i].Length();
+    if (NS_FAILED(rv)) {
+      break;
+    }
+  }
+
+  // propagate OnStopRequest and return
+  nextListener->OnStopRequest(mContentRequest, mContentContext, rv);
+}
+
 NS_IMETHODIMP
 ContentVerifier::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
 {
+  MOZ_CRASH("This OnStartRequest should've never been called!");
   return NS_OK;
 }
 
 NS_IMETHODIMP
 ContentVerifier::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext,
                                nsresult aStatus)
 {
-  // Verify the content:
-  // If this fails, we return an invalid signature error to load a fallback page.
-  // If everthing is good, we return a new stream to the next listener and kick
-  // that one of.
-  CSV_LOG(("VerifySignedContent, b64signature: %s\n", mSignature.get()));
-  CSV_LOG(("VerifySignedContent, key: \n[\n%s\n]\n", mKey.get()));
-  bool verified = false;
-  nsresult rv = End(&verified);
-  if (NS_FAILED(rv) || !verified || NS_FAILED(aStatus)) {
-    // cancel the request and return error
-    if (NS_FAILED(aStatus)) {
-      rv = aStatus;
-    } else {
-      rv = NS_ERROR_INVALID_SIGNATURE;
-    }
-    CSV_LOG(("failed to verify content\n"));
-    mNextListener->OnStartRequest(aRequest, aContext);
-    mNextListener->OnStopRequest(aRequest, aContext, rv);
-    return NS_ERROR_INVALID_SIGNATURE;
-  }
-  CSV_LOG(("Successfully verified content signature.\n"));
-
-  // start the next listener
-  rv = mNextListener->OnStartRequest(aRequest, aContext);
-  if (NS_SUCCEEDED(rv)) {
-    // We emptied aInStr so we have to create a new one from buf to hand it
-    // to the consuming listener.
-    for (uint32_t i = 0; i < mContent.Length(); ++i) {
-      nsCOMPtr<nsIInputStream> oInStr;
-      rv = NS_NewCStringInputStream(getter_AddRefs(oInStr), mContent[i]);
-      if (NS_FAILED(rv)) {
-        break;
-      }
-      // let the next listener know that there is data in oInStr
-      rv = mNextListener->OnDataAvailable(aRequest, aContext, oInStr, 0,
-                                          mContent[i].Length());
-      if (NS_FAILED(rv)) {
-        break;
-      }
-    }
+  // If we don't have a next listener, we handed off this request already.
+  // Return, there's nothing to do here.
+  if (!mNextListener) {
+    return NS_OK;
   }
 
-  // propagate OnStopRequest and return
-  return mNextListener->OnStopRequest(aRequest, aContext, rv);
+  if (NS_FAILED(aStatus)) {
+    CSV_LOG(("Stream failed\n"));
+    nsCOMPtr<nsIStreamListener> nextListener;
+    nextListener.swap(mNextListener);
+    return nextListener->OnStopRequest(aRequest, aContext, aStatus);
+  }
+
+  mContentRead = true;
+
+  // If the ContentSignatureVerifier is initialised, finish the verification.
+  if (mContextCreated) {
+    FinishSignature();
+    return aStatus;
+  }
+
+  return NS_OK;
 }
 
 NS_IMETHODIMP
 ContentVerifier::OnDataAvailable(nsIRequest* aRequest, nsISupports* aContext,
                                  nsIInputStream* aInputStream, uint64_t aOffset,
                                  uint32_t aCount)
 {
   // buffer the entire stream
   uint32_t read;
   nsresult rv = aInputStream->ReadSegments(AppendNextSegment, &mContent, aCount,
                                            &read);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
-  // update the signature verifier
-  return Update(mContent[mContent.Length()-1]);
-}
-
-/**
- * ContentVerifier logic and utils
- */
-
-nsresult
-ContentVerifier::GetVerificationKey(const nsAString& aKeyId)
-{
-  // get verification keys from the pref and see if we have |aKeyId|
-  nsCharSeparatedTokenizer tokenizerVK(mVks, ';');
-  while (tokenizerVK.hasMoreTokens()) {
-    nsDependentSubstring token = tokenizerVK.nextToken();
-    nsCharSeparatedTokenizer tokenizerKey(token, '=');
-    nsString prefKeyId;
-    if (tokenizerKey.hasMoreTokens()) {
-      prefKeyId = tokenizerKey.nextToken();
-    }
-    nsString key;
-    if (tokenizerKey.hasMoreTokens()) {
-      key = tokenizerKey.nextToken();
-    }
-    if (prefKeyId.Equals(aKeyId)) {
-      mKey.Assign(NS_ConvertUTF16toUTF8(key));
-      return NS_OK;
-    }
-  }
-
-  // we didn't find the appropriate key
-  return NS_ERROR_INVALID_SIGNATURE;
-}
-
-nsresult
-ContentVerifier::ParseContentSignatureHeader(
-  const nsAString& aContentSignatureHeader)
-{
-  // We only support p384 ecdsa according to spec
-  NS_NAMED_LITERAL_CSTRING(keyid_var, "keyid");
-  NS_NAMED_LITERAL_CSTRING(signature_var, "p384ecdsa");
-
-  nsAutoString contentSignature;
-  nsAutoString keyId;
-  nsAutoCString header = NS_ConvertUTF16toUTF8(aContentSignatureHeader);
-  nsSecurityHeaderParser parser(header.get());
-  nsresult rv = parser.Parse();
-  if (NS_FAILED(rv)) {
-    CSV_LOG(("ContentVerifier: could not parse ContentSignature header\n"));
-    return NS_ERROR_INVALID_SIGNATURE;
-  }
-  LinkedList<nsSecurityHeaderDirective>* directives = parser.GetDirectives();
-
-  for (nsSecurityHeaderDirective* directive = directives->getFirst();
-       directive != nullptr; directive = directive->getNext()) {
-    CSV_LOG(("ContentVerifier: found directive %s\n", directive->mName.get()));
-    if (directive->mName.Length() == keyid_var.Length() &&
-        directive->mName.EqualsIgnoreCase(keyid_var.get(),
-                                          keyid_var.Length())) {
-      if (!keyId.IsEmpty()) {
-        CSV_LOG(("ContentVerifier: found two keyIds\n"));
-        return NS_ERROR_INVALID_SIGNATURE;
-      }
-
-      CSV_LOG(("ContentVerifier: found a keyid directive\n"));
-      keyId = NS_ConvertUTF8toUTF16(directive->mValue);
-      rv = GetVerificationKey(keyId);
-      NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_SIGNATURE);
-    }
-    if (directive->mName.Length() == signature_var.Length() &&
-        directive->mName.EqualsIgnoreCase(signature_var.get(),
-                                          signature_var.Length())) {
-      if (!contentSignature.IsEmpty()) {
-        CSV_LOG(("ContentVerifier: found two ContentSignatures\n"));
-        return NS_ERROR_INVALID_SIGNATURE;
-      }
-
-      CSV_LOG(("ContentVerifier: found a ContentSignature directive\n"));
-      contentSignature = NS_ConvertUTF8toUTF16(directive->mValue);
-      mSignature = directive->mValue;
-    }
-  }
-
-  // we have to ensure that we found a key and a signature at this point
-  if (mKey.IsEmpty()) {
-    CSV_LOG(("ContentVerifier: got a Content-Signature header but didn't find "
-             "an appropriate key.\n"));
-    return NS_ERROR_INVALID_SIGNATURE;
-  }
-  if (mSignature.IsEmpty()) {
-    CSV_LOG(("ContentVerifier: got a Content-Signature header but didn't find "
-             "a signature.\n"));
-    return NS_ERROR_INVALID_SIGNATURE;
+  // Update the signature verifier if the context has been created.
+  if (mContextCreated) {
+    return mVerifier->Update(mContent.LastElement());
   }
 
   return NS_OK;
 }
 
-/**
- * Parse signature, public key, and algorithm data for input to verification
- * functions in VerifyData and CreateContext.
- *
- * https://datatracker.ietf.org/doc/draft-thomson-http-content-signature/
- * If aSignature is a content signature, the function returns
- * NS_ERROR_INVALID_SIGNATURE if anything goes wrong. Only p384 with sha384
- * is supported and aSignature is a raw signature (r||s).
- */
-nsresult
-ContentVerifier::ParseInput(ScopedSECKEYPublicKey& aPublicKeyOut,
-                            ScopedSECItem& aSignatureItemOut,
-                            SECOidTag& aOidOut,
-                            const nsNSSShutDownPreventionLock&)
+NS_IMETHODIMP
+ContentVerifier::ContextCreated(bool successful)
 {
-  // Base 64 decode the key
-  ScopedSECItem keyItem(::SECITEM_AllocItem(nullptr, nullptr, 0));
-  if (!keyItem ||
-      !NSSBase64_DecodeBuffer(nullptr, keyItem,
-                              mKey.get(),
-                              mKey.Length())) {
-    return NS_ERROR_INVALID_SIGNATURE;
-  }
+  MOZ_ASSERT(NS_IsMainThread());
+  if (!successful) {
+    // If we don't have a next listener, the request has been handed off already.
+    if (!mNextListener) {
+      return NS_OK;
+    }
+    // Get local reference to mNextListener and null it to ensure that we don't
+    // call it twice.
+    nsCOMPtr<nsIStreamListener> nextListener;
+    nextListener.swap(mNextListener);
+
+    // Make sure that OnStartRequest was called and we have a request.
+    MOZ_ASSERT(mContentRequest);
 
-  // Extract the public key from the keyItem
-  ScopedCERTSubjectPublicKeyInfo pki(
-    SECKEY_DecodeDERSubjectPublicKeyInfo(keyItem));
-  if (!pki) {
-    return NS_ERROR_INVALID_SIGNATURE;
-  }
-  aPublicKeyOut = SECKEY_ExtractPublicKey(pki.get());
+    // In this case something went wrong with the cert. Let's stop this load.
+    CSV_LOG(("failed to get a valid cert chain\n"));
+    if (mContentRequest && nextListener) {
+      mContentRequest->Cancel(NS_ERROR_INVALID_SIGNATURE);
+      nsresult rv = nextListener->OnStopRequest(mContentRequest, mContentContext,
+                                                NS_ERROR_INVALID_SIGNATURE);
+      mContentRequest = nullptr;
+      mContentContext = nullptr;
+      return rv;
+    }
 
-  // in case we were not able to extract a key
-  if (!aPublicKeyOut) {
-    return NS_ERROR_INVALID_SIGNATURE;
-  }
-
-  // Base 64 decode the signature
-  ScopedSECItem rawSignatureItem(::SECITEM_AllocItem(nullptr, nullptr, 0));
-  if (!rawSignatureItem ||
-      !NSSBase64_DecodeBuffer(nullptr, rawSignatureItem,
-                              mSignature.get(),
-                              mSignature.Length())) {
-    return NS_ERROR_INVALID_SIGNATURE;
+    // We should never get here!
+    MOZ_ASSERT_UNREACHABLE(
+      "ContentVerifier was used without getting OnStartRequest!");
+    return NS_OK;
   }
 
-  // get signature object and oid
-  if (!aSignatureItemOut) {
-    return NS_ERROR_INVALID_SIGNATURE;
-  }
-  // We have a raw ecdsa signature r||s so we have to DER-encode it first
-  // Note that we have to check rawSignatureItem->len % 2 here as
-  // DSAU_EncodeDerSigWithLen asserts this
-  if (rawSignatureItem->len == 0 || rawSignatureItem->len % 2 != 0) {
-    return NS_ERROR_INVALID_SIGNATURE;
-  }
-  if (DSAU_EncodeDerSigWithLen(aSignatureItemOut, rawSignatureItem,
-                               rawSignatureItem->len) != SECSuccess) {
-    return NS_ERROR_INVALID_SIGNATURE;
-  }
-  aOidOut = SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE;
-
-  return NS_OK;
-}
-
-/**
- * Create a context for a signature verification.
- * It sets signature, public key, and algorithms that should be used to verify
- * the data. It also updates the verification buffer with the content-signature
- * prefix.
- */
-nsresult
-ContentVerifier::CreateContext()
-{
-  nsNSSShutDownPreventionLock locker;
-  if (isAlreadyShutDown()) {
-    return NS_ERROR_INVALID_SIGNATURE;
+  // In this case the content verifier is initialised and we have to feed it
+  // the buffered content.
+  mContextCreated = true;
+  for (size_t i = 0; i < mContent.Length(); ++i) {
+    if (NS_FAILED(mVerifier->Update(mContent[i]))) {
+      // Bail out if this fails. We can't return an error here, but if this
+      // failed, NS_ERROR_INVALID_SIGNATURE is returned in FinishSignature.
+      break;
+    }
   }
 
-  // Bug 769521: We have to change b64 url to regular encoding as long as we
-  // don't have a b64 url decoder. This should change soon, but in the meantime
-  // we have to live with this.
-  mSignature.ReplaceChar('-', '+');
-  mSignature.ReplaceChar('_', '/');
-
-  ScopedSECKEYPublicKey publicKey;
-  ScopedSECItem signatureItem(::SECITEM_AllocItem(nullptr, nullptr, 0));
-  SECOidTag oid;
-  nsresult rv = ParseInput(publicKey, signatureItem, oid, locker);
-  if (NS_FAILED(rv)) {
-    return NS_ERROR_INVALID_SIGNATURE;
-  }
-
-  mCx = UniqueVFYContext(VFY_CreateContext(publicKey, signatureItem, oid, NULL));
-  if (!mCx) {
-    return NS_ERROR_INVALID_SIGNATURE;
-  }
-
-  if (VFY_Begin(mCx.get()) != SECSuccess) {
-    return NS_ERROR_INVALID_SIGNATURE;
-  }
-
-  // add the prefix to the verification buffer
-  return Update(kPREFIX);
-}
-
-/**
- * Add data to the context that should be verified.
- */
-nsresult
-ContentVerifier::Update(const nsACString& aData)
-{
-  nsNSSShutDownPreventionLock locker;
-  if (isAlreadyShutDown()) {
-    return NS_ERROR_INVALID_SIGNATURE;
-  }
-
-  if (!aData.IsEmpty()) {
-    if (VFY_Update(mCx.get(),
-                   (const unsigned char*)nsPromiseFlatCString(aData).get(),
-                   aData.Length()) != SECSuccess) {
-      return NS_ERROR_INVALID_SIGNATURE;
-    }
+  // We read all content, let's verify the signature.
+  if (mContentRead) {
+    FinishSignature();
   }
 
   return NS_OK;
 }
-
-/**
- * Finish signature verification and return the result in _retval.
- */
-nsresult
-ContentVerifier::End(bool* _retval)
-{
-  nsNSSShutDownPreventionLock locker;
-  if (isAlreadyShutDown()) {
-    return NS_ERROR_INVALID_SIGNATURE;
-  }
-
-  *_retval = (VFY_End(mCx.get()) == SECSuccess);
-
-  return NS_OK;
-}
\ No newline at end of file
--- a/dom/security/ContentVerifier.h
+++ b/dom/security/ContentVerifier.h
@@ -2,97 +2,63 @@
 /* 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_dom_ContentVerifier_h
 #define mozilla_dom_ContentVerifier_h
 
 #include "nsCOMPtr.h"
+#include "nsIContentSignatureVerifier.h"
 #include "nsIObserver.h"
 #include "nsIStreamListener.h"
-#include "nsNSSShutDown.h"
 #include "nsString.h"
 #include "nsTArray.h"
-#include "ScopedNSSTypes.h"
 
 /**
  * Mediator intercepting OnStartRequest in nsHttpChannel, blocks until all
  * data is read from the input stream, verifies the content signature and
  * releases the request to the next listener if the verification is successful.
  * If the verification fails or anything else goes wrong, a
  * NS_ERROR_INVALID_SIGNATURE is thrown.
  */
 class ContentVerifier : public nsIStreamListener
-                      , public nsNSSShutDownObject
+                      , public nsIContentSignatureReceiverCallback
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSISTREAMLISTENER
   NS_DECL_NSIREQUESTOBSERVER
+  NS_DECL_NSICONTENTSIGNATURERECEIVERCALLBACK
 
   explicit ContentVerifier(nsIStreamListener* aMediatedListener,
                            nsISupports* aMediatedContext)
     : mNextListener(aMediatedListener)
-    , mContext(aMediatedContext)
-    , mCx(nullptr) {}
-
-  nsresult Init(const nsAString& aContentSignatureHeader);
+    , mContextCreated(false)
+    , mContentRead(false) {}
 
-  // nsNSSShutDownObject
-  virtual void virtualDestroyNSSReference() override
-  {
-    destructorSafeDestroyNSSReference();
-  }
+  nsresult Init(const nsACString& aContentSignatureHeader, nsIRequest* aRequest,
+                nsISupports* aContext);
 
 protected:
-  virtual ~ContentVerifier()
-  {
-    nsNSSShutDownPreventionLock locker;
-    if (isAlreadyShutDown()) {
-      return;
-    }
-    destructorSafeDestroyNSSReference();
-    shutdown(calledFromObject);
-  }
-
-  void destructorSafeDestroyNSSReference()
-  {
-    mCx = nullptr;
-  }
+  virtual ~ContentVerifier() {}
 
 private:
-  nsresult ParseContentSignatureHeader(const nsAString& aContentSignatureHeader);
-  nsresult GetVerificationKey(const nsAString& aKeyId);
-
-  // utility function to parse input before put into verification functions
-  nsresult ParseInput(mozilla::ScopedSECKEYPublicKey& aPublicKeyOut,
-                      mozilla::ScopedSECItem& aSignatureItemOut,
-                      SECOidTag& aOidOut,
-                      const nsNSSShutDownPreventionLock&);
+  void FinishSignature();
 
-  // create a verifier context and store it in mCx
-  nsresult CreateContext();
-
-  // Adds data to the context that was used to generate the signature.
-  nsresult Update(const nsACString& aData);
-
-  // Finalises the signature and returns the result of the signature
-  // verification.
-  nsresult End(bool* _retval);
-
+  // buffered content to verify
+  FallibleTArray<nsCString> mContent;
   // content and next listener for nsIStreamListener
   nsCOMPtr<nsIStreamListener> mNextListener;
-  nsCOMPtr<nsISupports> mContext;
-
-  // verifier context for incrementel verifications
-  mozilla::UniqueVFYContext mCx;
-  // buffered content to verify
-  FallibleTArray<nsCString> mContent;
-  // signature to verify
-  nsCString mSignature;
-  // verification key
-  nsCString mKey;
-  // verification key preference
-  nsString mVks;
+  // the verifier
+  nsCOMPtr<nsIContentSignatureVerifier> mVerifier;
+  // holding a pointer to the content request and context to resume/cancel it
+  nsCOMPtr<nsIRequest> mContentRequest;
+  nsCOMPtr<nsISupports> mContentContext;
+  // Semaphors to indicate that the verifying context was created, the entire
+  // content was read resp. The context gets created by ContentSignatureVerifier
+  // and mContextCreated is set in the ContextCreated callback. The content is
+  // read, i.e. mContentRead is set, when the content OnStopRequest is called.
+  bool mContextCreated;
+  bool mContentRead;
 };
 
 #endif /* mozilla_dom_ContentVerifier_h */
--- a/dom/security/nsCSPUtils.cpp
+++ b/dom/security/nsCSPUtils.cpp
@@ -1,15 +1,16 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 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 "nsAttrValue.h"
+#include "nsCharSeparatedTokenizer.h"
 #include "nsContentUtils.h"
 #include "nsCSPUtils.h"
 #include "nsDebug.h"
 #include "nsIConsoleService.h"
 #include "nsICryptoHash.h"
 #include "nsIScriptError.h"
 #include "nsIServiceManager.h"
 #include "nsIStringBundle.h"
--- a/dom/security/test/contentverifier/browser.ini
+++ b/dom/security/test/contentverifier/browser.ini
@@ -5,12 +5,15 @@ support-files =
   file_about_newtab_bad.html
   file_about_newtab_bad_csp.html
   file_about_newtab_bad_csp_signature
   file_about_newtab_good_signature
   file_about_newtab_bad_signature
   file_about_newtab_broken_signature
   file_about_newtab_sri.html
   file_about_newtab_sri_signature
+  goodChain.pem
+  head.js
   script.js
   style.css
 
 [browser_verify_content_about_newtab.js]
+[browser_verify_content_about_newtab2.js]
--- a/dom/security/test/contentverifier/browser_verify_content_about_newtab.js
+++ b/dom/security/test/contentverifier/browser_verify_content_about_newtab.js
@@ -1,243 +1,20 @@
-/*
- * Test Content-Signature for remote about:newtab
- *  - Bug 1226928 - allow about:newtab to load remote content
- *
- * This tests content-signature verification on remote about:newtab in the
- * following cases (see TESTS, all failed loads display about:blank fallback):
- * - good case (signature should verify and correct page is displayed)
- * - reload of newtab when the siganture was invalidated after the last correct
- *   load
- * - malformed content-signature header
- * - malformed keyid directive
- * - malformed p384ecdsa directive
- * - wrong signature (this is not a siganture for the delivered document)
- * - invalid signature (this is not even a signature)
- * - loading a file that doesn't fit the key or signature
- * - cache poisoning (load a malicious remote page not in newtab, subsequent
- *   newtab load has to load the fallback)
- */
-
-const ABOUT_NEWTAB_URI = "about:newtab";
-
-const BASE = "https://example.com/browser/dom/security/test/contentverifier/file_contentserver.sjs?";
-const URI_GOOD = BASE + "sig=good&key=good&file=good&header=good";
-
-const INVALIDATE_FILE = BASE + "invalidateFile=yep";
-const VALIDATE_FILE = BASE + "validateFile=yep";
-
-const URI_HEADER_BASE = BASE + "sig=good&key=good&file=good&header=";
-const URI_ERROR_HEADER = URI_HEADER_BASE + "error";
-const URI_KEYERROR_HEADER = URI_HEADER_BASE + "errorInKeyid";
-const URI_SIGERROR_HEADER = URI_HEADER_BASE + "errorInSignature";
-const URI_NO_HEADER = URI_HEADER_BASE + "noHeader";
-
-const URI_BAD_SIG = BASE + "sig=bad&key=good&file=good&header=good";
-const URI_BROKEN_SIG = BASE + "sig=broken&key=good&file=good&header=good";
-const URI_BAD_KEY = BASE + "sig=good&key=bad&file=good&header=good";
-const URI_BAD_FILE = BASE + "sig=good&key=good&file=bad&header=good";
-const URI_BAD_ALL = BASE + "sig=bad&key=bad&file=bad&header=bad";
-const URI_BAD_CSP = BASE + "sig=bad-csp&key=good&file=bad-csp&header=good";
-
-const URI_BAD_FILE_CACHED = BASE + "sig=good&key=good&file=bad&header=good&cached=true";
-
-const GOOD_ABOUT_STRING = "Just a fully good testpage for Bug 1226928";
-const BAD_ABOUT_STRING = "Just a bad testpage for Bug 1226928";
-const ABOUT_BLANK = "<head></head><body></body>";
-
-const URI_CLEANUP = BASE + "cleanup=true";
-const CLEANUP_DONE = "Done";
-
-const URI_SRI = BASE + "sig=sri&key=good&file=sri&header=good";
-const STYLESHEET_WITHOUT_SRI_BLOCKED = "Stylesheet without SRI blocked";
-const STYLESHEET_WITH_SRI_BLOCKED = "Stylesheet with SRI blocked";
-const STYLESHEET_WITH_SRI_LOADED = "Stylesheet with SRI loaded";
-const SCRIPT_WITHOUT_SRI_BLOCKED = "Script without SRI blocked";
-const SCRIPT_WITH_SRI_BLOCKED = "Script with SRI blocked";
-const SCRIPT_WITH_SRI_LOADED = "Script with SRI loaded";
-
-const CSP_TEST_SUCCESS_STRING = "CSP violation test succeeded.";
-
-// Needs to sync with pref "security.signed_content.CSP.default".
-const SIGNED_CONTENT_CSP = `{"csp-policies":[{"report-only":false,"script-src":["https://example.com","'unsafe-inline'"],"style-src":["https://example.com"]}]}`;
 
 const TESTS = [
   // { newtab (aboutURI) or regular load (url) : url,
   //   testStrings : expected strings in the loaded page }
   { "aboutURI" : URI_GOOD, "testStrings" : [GOOD_ABOUT_STRING] },
   { "aboutURI" : URI_ERROR_HEADER, "testStrings" : [ABOUT_BLANK] },
-  { "aboutURI" : URI_KEYERROR_HEADER, "testStrings" : [ABOUT_BLANK] },
-  { "aboutURI" : URI_SIGERROR_HEADER, "testStrings" : [ABOUT_BLANK] },
-  { "aboutURI" : URI_NO_HEADER, "testStrings" : [ABOUT_BLANK] },
-  { "aboutURI" : URI_BAD_SIG, "testStrings" : [ABOUT_BLANK] },
-  { "aboutURI" : URI_BROKEN_SIG, "testStrings" : [ABOUT_BLANK] },
-  { "aboutURI" : URI_BAD_KEY, "testStrings" : [ABOUT_BLANK] },
-  { "aboutURI" : URI_BAD_FILE, "testStrings" : [ABOUT_BLANK] },
-  { "aboutURI" : URI_BAD_ALL, "testStrings" : [ABOUT_BLANK] },
   { "url" : URI_BAD_FILE_CACHED, "testStrings" : [BAD_ABOUT_STRING] },
   { "aboutURI" : URI_BAD_FILE_CACHED, "testStrings" : [ABOUT_BLANK] },
   { "aboutURI" : URI_GOOD, "testStrings" : [GOOD_ABOUT_STRING] },
   { "aboutURI" : URI_SRI, "testStrings" : [
     STYLESHEET_WITHOUT_SRI_BLOCKED,
     STYLESHEET_WITH_SRI_LOADED,
     SCRIPT_WITHOUT_SRI_BLOCKED,
     SCRIPT_WITH_SRI_LOADED,
     ]},
   { "aboutURI" : URI_BAD_CSP, "testStrings" : [CSP_TEST_SUCCESS_STRING] },
   { "url" : URI_CLEANUP, "testStrings" : [CLEANUP_DONE] },
 ];
 
-var browser = null;
-var aboutNewTabService = Cc["@mozilla.org/browser/aboutnewtab-service;1"]
-                           .getService(Ci.nsIAboutNewTabService);
-
-function pushPrefs(...aPrefs) {
-  return new Promise((resolve) => {
-    SpecialPowers.pushPrefEnv({"set": aPrefs}, resolve);
-  });
-}
-
-/*
- * run tests with input from TESTS
- */
-function doTest(aExpectedStrings, reload, aUrl, aNewTabPref) {
-  // set about:newtab location for this test if it's a newtab test
-  if (aNewTabPref) {
-    aboutNewTabService.newTabURL = aNewTabPref;
-  }
-
-  // set prefs
-  yield pushPrefs(
-      ["browser.newtabpage.remote.content-signing-test", true],
-      ["browser.newtabpage.remote", true], [
-        "browser.newtabpage.remote.keys",
-        "RemoteNewTabNightlyv0=BO9QHuP6E2eLKybql8iuD4o4Np9YFDfW3D+k" +
-        "a70EcXXTqZcikc7Am1CwyP1xBDTpEoe6gb9SWzJmaDW3dNh1av2u90VkUM" +
-        "B7aHIrImjTjLNg/1oC8GRcTKM4+WzbKF00iA==;OtherKey=eKQJ2fNSId" +
-        "CFzL6N326EzZ/5LCeFU5eyq3enwZ5MLmvOw+3gycr4ZVRc36/EiSPsQYHE" +
-        "3JvJs1EKs0QCaguHFOZsHwqXMPicwp/gLdeYbuOmN2s1SEf/cxw8GtcxSA" +
-        "kG;RemoteNewTab=MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE4k3FmG7dFo" +
-        "Ot3Tuzl76abTRtK8sb/r/ibCSeVKa96RbrOX2ciscz/TT8wfqBYS/8cN4z" +
-        "Me1+f7wRmkNrCUojZR1ZKmYM2BeiUOMlMoqk2O7+uwsn1DwNQSYP58TkvZt6"
-      ]);
-
-  if (aNewTabPref === URI_BAD_CSP) {
-    // Use stricter CSP to test CSP violation.
-    yield pushPrefs(["security.signed_content.CSP.default", "script-src 'self'; style-src 'self'"]);
-  } else {
-    // Use weaker CSP to test normal content.
-    yield pushPrefs(["security.signed_content.CSP.default", "script-src 'self' 'unsafe-inline'; style-src 'self'"]);
-  }
-
-  // start the test
-  yield BrowserTestUtils.withNewTab({
-      gBrowser,
-      url: aUrl,
-    },
-    function * (browser) {
-      // check if everything's set correct for testing
-      ok(Services.prefs.getBoolPref(
-          "browser.newtabpage.remote.content-signing-test"),
-          "sanity check: remote newtab signing test should be used");
-      ok(Services.prefs.getBoolPref("browser.newtabpage.remote"),
-          "sanity check: remote newtab should be used");
-      // we only check this if we really do a newtab test
-      if (aNewTabPref) {
-        ok(aboutNewTabService.overridden,
-            "sanity check: default URL for about:newtab should be overriden");
-        is(aboutNewTabService.newTabURL, aNewTabPref,
-            "sanity check: default URL for about:newtab should return the new URL");
-      }
-
-      // Every valid remote newtab page must have built-in CSP.
-      let shouldHaveCSP = ((aUrl === ABOUT_NEWTAB_URI) &&
-                          (aNewTabPref === URI_GOOD || aNewTabPref === URI_SRI));
-
-      if (shouldHaveCSP) {
-        is(browser.contentDocument.nodePrincipal.cspJSON, SIGNED_CONTENT_CSP,
-           "Valid remote newtab page must have built-in CSP.");
-      }
-
-      yield ContentTask.spawn(
-          browser, aExpectedStrings, function * (aExpectedStrings) {
-            for (let expectedString of aExpectedStrings) {
-              ok(content.document.documentElement.innerHTML.includes(expectedString),
-                 "Expect the following value in the result\n" + expectedString +
-                 "\nand got " + content.document.documentElement.innerHTML);
-            }
-          });
-
-      // for good test cases we check if a reload fails if the remote page
-      // changed from valid to invalid in the meantime
-      if (reload) {
-        yield BrowserTestUtils.withNewTab({
-            gBrowser,
-            url: INVALIDATE_FILE,
-          },
-          function * (browser2) {
-            yield ContentTask.spawn(browser2, null, function * () {
-              ok(content.document.documentElement.innerHTML.includes("Done"),
-                 "Expect the following value in the result\n" + "Done" +
-                 "\nand got " + content.document.documentElement.innerHTML);
-            });
-          }
-        );
-
-        browser.reload();
-        yield BrowserTestUtils.browserLoaded(browser);
-
-        let expectedStrings = [ABOUT_BLANK];
-        if (aNewTabPref == URI_SRI) {
-          expectedStrings = [
-            STYLESHEET_WITHOUT_SRI_BLOCKED,
-            STYLESHEET_WITH_SRI_BLOCKED,
-            SCRIPT_WITHOUT_SRI_BLOCKED,
-            SCRIPT_WITH_SRI_BLOCKED
-          ];
-        }
-        yield ContentTask.spawn(browser, expectedStrings,
-          function * (expectedStrings) {
-            for (let expectedString of expectedStrings) {
-              ok(content.document.documentElement.innerHTML.includes(expectedString),
-                 "Expect the following value in the result\n" + expectedString +
-                 "\nand got " + content.document.documentElement.innerHTML);
-            }
-          }
-        );
-
-        yield BrowserTestUtils.withNewTab({
-            gBrowser,
-            url: VALIDATE_FILE,
-          },
-          function * (browser2) {
-            yield ContentTask.spawn(browser2, null, function * () {
-              ok(content.document.documentElement.innerHTML.includes("Done"),
-                 "Expect the following value in the result\n" + "Done" +
-                 "\nand got " + content.document.documentElement.innerHTML);
-              });
-          }
-        );
-      }
-    }
-  );
-}
-
-add_task(function * test() {
-  // run tests from TESTS
-  for (let i = 0; i < TESTS.length; i++) {
-    let testCase = TESTS[i];
-    let url = "", aNewTabPref = "";
-    let reload = false;
-    var aExpectedStrings = testCase.testStrings;
-    if (testCase.aboutURI) {
-      url = ABOUT_NEWTAB_URI;
-      aNewTabPref = testCase.aboutURI;
-      if (aNewTabPref == URI_GOOD || aNewTabPref == URI_SRI) {
-        reload = true;
-      }
-    } else {
-      url = testCase.url;
-    }
-
-    yield doTest(aExpectedStrings, reload, url, aNewTabPref);
-  }
-});
+add_task(runTests);
new file mode 100644
--- /dev/null
+++ b/dom/security/test/contentverifier/browser_verify_content_about_newtab2.js
@@ -0,0 +1,19 @@
+
+const TESTS = [
+  // { newtab (aboutURI) or regular load (url) : url,
+  //   testStrings : expected strings in the loaded page }
+  { "aboutURI" : URI_GOOD, "testStrings" : [GOOD_ABOUT_STRING] },
+  { "aboutURI" : URI_ERROR_HEADER, "testStrings" : [ABOUT_BLANK] },
+  { "aboutURI" : URI_KEYERROR_HEADER, "testStrings" : [ABOUT_BLANK] },
+  { "aboutURI" : URI_SIGERROR_HEADER, "testStrings" : [ABOUT_BLANK] },
+  { "aboutURI" : URI_NO_HEADER, "testStrings" : [ABOUT_BLANK] },
+  { "aboutURI" : URI_BAD_SIG, "testStrings" : [ABOUT_BLANK] },
+  { "aboutURI" : URI_BROKEN_SIG, "testStrings" : [ABOUT_BLANK] },
+  { "aboutURI" : URI_BAD_X5U, "testStrings" : [ABOUT_BLANK] },
+  { "aboutURI" : URI_HTTP_X5U, "testStrings" : [ABOUT_BLANK] },
+  { "aboutURI" : URI_BAD_FILE, "testStrings" : [ABOUT_BLANK] },
+  { "aboutURI" : URI_BAD_ALL, "testStrings" : [ABOUT_BLANK] },
+  { "url" : URI_CLEANUP, "testStrings" : [CLEANUP_DONE] },
+];
+
+add_task(runTests);
--- a/dom/security/test/contentverifier/file_about_newtab_bad_csp_signature
+++ b/dom/security/test/contentverifier/file_about_newtab_bad_csp_signature
@@ -1,1 +1,1 @@
-8qXVAqzuF3TsF6C750u_v_JiRu90WJXf_0xT9x0S4Fgmvolgtfu-KSWq3lYpmk2dxO8u64zaHM3iguZdWAqcSL82RFtV7OPiprt16omCbHCKfVi-Bt_rXILRlexgmRl_
\ No newline at end of file
+oiypz3lb-IyJsmKNsnlp2zDrqncste8yONn9WUE6ksgJWMhSEQ9lp8vRqN0W3JPwJb6uSk16RI-tDv7uy0jxon5jL1BZpqlqIpvimg7FCQEedMKoHZwtE9an-e95sOTd
\ No newline at end of file
--- a/dom/security/test/contentverifier/file_about_newtab_good_signature
+++ b/dom/security/test/contentverifier/file_about_newtab_good_signature
@@ -1,1 +1,1 @@
-XBKzej3i6TAFZc3VZsuCekn-4dYWJBE4-b3OOtKrOV-JIzIvAnAhnOV1aj-kEm07kh-FciIxV-Xk2QUQlRQzHO7oW7E4mXkMKkbbAcvL0CFrItTObhfhKnBnpAE9ql1O
\ No newline at end of file
+-mqpvTYdZX4HYQDW1nScojL7ICw5yj8UF2gzxyLbSCx9UIfHH-gWZ40F_PFtqjHxoC1J3dHDb3VedVhOYczdaLrNKbRvPrlnkdGx7Rl8qEBrtZpF1py1Z9uAGoCrgUHa
\ No newline at end of file
--- a/dom/security/test/contentverifier/file_about_newtab_sri_signature
+++ b/dom/security/test/contentverifier/file_about_newtab_sri_signature
@@ -1,1 +1,1 @@
-i5jOnrZWwyNwrTcIjfJ6fUR-8MhhvhtMvQbdrUD7j8aHTybNolv25v9NwJAT6rVU6kgkxmD_st9Kla086CQmzYQdLhKfzgLbTDXz0-1j23fQnyjsP1_4MNIu2xTea11p
\ No newline at end of file
+yoIyAYiiEzdP1zpkRy3KaqdsjUy62Notku89cytwVwcH0x6fKsMCdM-df1wbk9N28CSTaIOW5kcSenFy5K3nU-zPIoqZDjQo6aSjF8hF6lrw1a1xbhfl9K3g4YJsuWsO
\ No newline at end of file
--- a/dom/security/test/contentverifier/file_contentserver.sjs
+++ b/dom/security/test/contentverifier/file_contentserver.sjs
@@ -1,37 +1,51 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/. */
 // sjs for remote about:newtab (bug 1226928)
+"use strict";
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 Cu.import("resource://gre/modules/NetUtil.jsm");
 Cu.import("resource://gre/modules/FileUtils.jsm");
 Cu.importGlobalProperties(["URLSearchParams"]);
 
 const path = "browser/dom/security/test/contentverifier/";
 
 const goodFileName = "file_about_newtab.html";
 const goodFileBase = path + goodFileName;
 const goodFile = FileUtils.getDir("TmpD", [], true);
 goodFile.append(goodFileName);
 const goodSignature = path + "file_about_newtab_good_signature";
-const goodKeyId = "RemoteNewTab";
+const goodX5UString = "\"https://example.com/browser/dom/security/test/contentverifier/file_contentserver.sjs?x5u=default\"";
 
 const scriptFileName = "script.js";
 const cssFileName = "style.css";
 const badFile = path + "file_about_newtab_bad.html";
 const brokenSignature = path + "file_about_newtab_broken_signature";
 const badSignature = path + "file_about_newtab_bad_signature";
-const badKeyId = "OldRemoteNewTabKey";
+const badX5UString = "\"https://example.com/browser/dom/security/test/contentverifier/file_contentserver.sjs?x5u=bad\"";
+const httpX5UString = "\"http://example.com/browser/dom/security/test/contentverifier/file_contentserver.sjs?x5u=default\"";
 
 const sriFile = path + "file_about_newtab_sri.html";
 const sriSignature = path + "file_about_newtab_sri_signature";
 
 const badCspFile = path + "file_about_newtab_bad_csp.html";
 const badCspSignature = path + "file_about_newtab_bad_csp_signature";
 
+// This cert chain is copied from
+// security/manager/ssl/tests/unit/test_content_signing/
+// using the certificates
+// * content_signing_remote_newtab_ee.pem
+// * content_signing_int.pem
+// * content_signing_root.pem
+const goodCertChainPath = path + "goodChain.pem";
+
 const tempFileNames = [goodFileName, scriptFileName, cssFileName];
 
 // we copy the file to serve as newtab to a temp directory because
 // we modify it during tests.
 setupTestFiles();
 
 function setupTestFiles() {
   for (let fileName of tempFileNames) {
@@ -110,24 +124,25 @@ function cleanupTestFiles() {
  * sig=good&key=good&file=good&header=good&cached=no to serve pages with
  * content signatures
  *
  * it further handles invalidateFile=yep and validateFile=yep to change the
  * served file
  */
 function handleRequest(request, response) {
   let params = new URLSearchParams(request.queryString);
-  let keyType = params.get("key");
+  let x5uType = params.get("x5u");
   let signatureType = params.get("sig");
   let fileType = params.get("file");
   let headerType = params.get("header");
   let cached = params.get("cached");
   let invalidateFile = params.get("invalidateFile");
   let validateFile = params.get("validateFile");
   let resource = params.get("resource");
+  let x5uParam = params.get("x5u");
 
   if (params.get("cleanup")) {
     cleanupTestFiles();
     response.setHeader("Content-Type", "text/html", false);
     response.write("Done");
     return;
   }
 
@@ -166,36 +181,46 @@ function handleRequest(request, response
         r = "Error";
       }
     }
     response.setHeader("Content-Type", "text/html", false);
     response.write(r);
     return;
   }
 
+  // we have to return the certificate chain on request for the x5u parameter
+  if (x5uParam && x5uParam == "default") {
+    response.setHeader("Cache-Control", "max-age=216000", false);
+    response.setHeader("Content-Type", "text/plain", false);
+    response.write(loadFile(getFileName(goodCertChainPath, "CurWorkD")));
+    return;
+  }
+
   // avoid confusing cache behaviours
   if (!cached) {
     response.setHeader("Cache-Control", "no-cache", false);
   } else {
     response.setHeader("Cache-Control", "max-age=3600", false);
   }
 
   // send HTML to test allowed/blocked behaviours
   response.setHeader("Content-Type", "text/html", false);
 
   // set signature header and key for Content-Signature header
   /* By default a good content-signature header is returned. Any broken return
    * value has to be indicated in the url.
    */
   let csHeader = "";
-  let keyId = goodKeyId;
+  let x5uString = goodX5UString;
   let signature = goodSignature;
   let file = goodFile;
-  if (keyType == "bad") {
-    keyId = badKeyId;
+  if (x5uType == "bad") {
+    x5uString = badX5UString;
+  } else if (x5uType == "http") {
+    x5uString = httpX5UString;
   }
   if (signatureType == "bad") {
     signature = badSignature;
   } else if (signatureType == "broken") {
     signature = brokenSignature;
   } else if (signatureType == "sri") {
     signature = sriSignature;
   } else if (signatureType == "bad-csp") {
@@ -206,29 +231,29 @@ function handleRequest(request, response
   } else if (fileType == "sri") {
     file = getFileName(sriFile, "CurWorkD");
   } else if (fileType == "bad-csp") {
     file = getFileName(badCspFile, "CurWorkD");
   }
 
   if (headerType == "good") {
     // a valid content-signature header
-    csHeader = "keyid=" + keyId + ";p384ecdsa=" +
+    csHeader = "x5u=" + x5uString + ";p384ecdsa=" +
                loadFile(getFileName(signature, "CurWorkD"));
   } else if (headerType == "error") {
     // this content-signature header is missing ; before p384ecdsa
-    csHeader = "keyid=" + keyId + "p384ecdsa=" +
+    csHeader = "x5u=" + x5uString + "p384ecdsa=" +
                loadFile(getFileName(signature, "CurWorkD"));
-  } else if (headerType == "errorInKeyid") {
+  } else if (headerType == "errorInX5U") {
     // this content-signature header is missing the keyid directive
-    csHeader = "keid=" + keyId + ";p384ecdsa=" +
+    csHeader = "x6u=" + x5uString + ";p384ecdsa=" +
                loadFile(getFileName(signature, "CurWorkD"));
   } else if (headerType == "errorInSignature") {
     // this content-signature header is missing the p384ecdsa directive
-    csHeader = "keyid=" + keyId + ";p385ecdsa=" +
+    csHeader = "x5u=" + x5uString + ";p385ecdsa=" +
                loadFile(getFileName(signature, "CurWorkD"));
   }
 
   if (csHeader) {
     response.setHeader("Content-Signature", csHeader, false);
   }
   let result = loadFile(file);
 
new file mode 100644
--- /dev/null
+++ b/dom/security/test/contentverifier/goodChain.pem
@@ -0,0 +1,51 @@
+-----BEGIN CERTIFICATE-----
+MIICSTCCATOgAwIBAgIUWQzTTfKLNZgX5ngi/ENiI2DO2kowCwYJKoZIhvcNAQEL
+MBExDzANBgNVBAMMBmludC1DQTAiGA8yMDE0MTEyNzAwMDAwMFoYDzIwMTcwMjA0
+MDAwMDAwWjAUMRIwEAYDVQQDDAllZS1pbnQtQ0EwdjAQBgcqhkjOPQIBBgUrgQQA
+IgNiAAShaHJDNitcexiJ83kVRhWhxz+0je6GPgIpFdtgjiUt5LcTLajOmOgxU05q
+nAwLCcjWOa3oMgbluoE0c6EfozDgXajJbkOD/ieHPalxA74oiM/wAvBa9xof3cyD
+dKpuqc6jRDBCMBMGA1UdJQQMMAoGCCsGAQUFBwMDMCsGA1UdEQQkMCKCIHJlbW90
+ZS1uZXd0YWItc2lnbmVyLm1vemlsbGEub3JnMAsGCSqGSIb3DQEBCwOCAQEAc2nE
+feYpA8WFyiPfZi56NgVgc8kXSKRNgplDtBHXK7gT7ICNQTSKkt+zHxnS9tAoXoix
+OGKsyp/8LNIYGMr4vHVNyOGnxuiLzAYjmDxXhp3t36xOFlU5Y7UaKf9G4feMXrNH
++q1SPYlP84keo1MaC5yhTZTTmJMKkRBsCbIVhfDnL3BUczxVZmk9F+7qK/trL222
+RoAaTZW5hdXUZrX630CYs1sQHWgL0B5rg2y9bwFk7toQ34JbjS0Z25e/MZUtFz19
+5tSjAZQHlLE6fAYZ3knrxF9xVMJCZf7gQqVphJzBtgy9yvTAtlMsrf6XS6sRRngz
+27HBxIpd4tYniYrtfg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIC0TCCAbugAwIBAgIULYyr3v/0zZ+XiR22NH7hOcnj2FcwCwYJKoZIhvcNAQEL
+MA0xCzAJBgNVBAMMAmNhMCIYDzIwMTQxMTI3MDAwMDAwWhgPMjAxNzAyMDQwMDAw
+MDBaMBExDzANBgNVBAMMBmludC1DQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBALqIUahEjhbWQf1utogGNhA9PBPZ6uQ1SrTs9WhXbCR7wcclqODYH72x
+nAabbhqG8mvir1p1a2pkcQh6pVqnRYf3HNUknAJ+zUP8HmnQOCApk6sgw0nk27lM
+wmtsDu0Vgg/xfq1pGrHTAjqLKkHup3DgDw2N/WYLK7AkkqR9uYhheZCxV5A90jvF
+4LhIH6g304hD7ycW2FW3ZlqqfgKQLzp7EIAGJMwcbJetlmFbt+KWEsB1MaMMkd20
+yvf8rR0l0wnvuRcOp2jhs3svIm9p47SKlWEd7ibWJZ2rkQhONsscJAQsvxaLL+Xx
+j5kXMbiz/kkj+nJRxDHVA6zaGAo17Y0CAwEAAaMlMCMwDAYDVR0TBAUwAwEB/zAT
+BgNVHSUEDDAKBggrBgEFBQcDAzALBgkqhkiG9w0BAQsDggEBADfRBKSM08JF6vqz
+0EA+KNc0XIEAWApuHuwX6XXWeLgo6QN4E/9qfrsaO+C366WT+JDsjDOi40wW46SA
+XbguxtZQeZasNDUWp/leZix4RSJoHB7OllG1rgZJfN76zKVaXRGUmyQObkMMOJZe
+wIA0OBURT8ik9Z89pD0IWrqscds71Edfjt0hHgg63wVvIaklReZXvFOD3VmSCPNn
+2wB6ZzECcbhJpnzxZdsoMSGH0C6apYnNNTjqZjO90JVm/Ph/7nbi/KncYXA6ccl6
+Jz2mfiAquWIua2+CzBGbqjZVSATTpWCp+cXQJE1xka+hWUaL5HPTq1bTULRFlauZ
+HGl5lJk=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICzTCCAbegAwIBAgIUIVkGGA8HiO3RIKGjdOjVi+d6EVkwCwYJKoZIhvcNAQEL
+MA0xCzAJBgNVBAMMAmNhMCIYDzIwMTQxMTI3MDAwMDAwWhgPMjAxNzAyMDQwMDAw
+MDBaMA0xCzAJBgNVBAMMAmNhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEAuohRqESOFtZB/W62iAY2ED08E9nq5DVKtOz1aFdsJHvBxyWo4NgfvbGcBptu
+Gobya+KvWnVramRxCHqlWqdFh/cc1SScAn7NQ/weadA4ICmTqyDDSeTbuUzCa2wO
+7RWCD/F+rWkasdMCOosqQe6ncOAPDY39ZgsrsCSSpH25iGF5kLFXkD3SO8XguEgf
+qDfTiEPvJxbYVbdmWqp+ApAvOnsQgAYkzBxsl62WYVu34pYSwHUxowyR3bTK9/yt
+HSXTCe+5Fw6naOGzey8ib2njtIqVYR3uJtYlnauRCE42yxwkBCy/Fosv5fGPmRcx
+uLP+SSP6clHEMdUDrNoYCjXtjQIDAQABoyUwIzAMBgNVHRMEBTADAQH/MBMGA1Ud
+JQQMMAoGCCsGAQUFBwMDMAsGCSqGSIb3DQEBCwOCAQEAlpbRzRIPnf43AwGfMvKP
+zOtntRy2nE9GlmY9I00uioHUnUrPLs8aw3UDtyiDWMGqcYysXGx9EX2Vk0POS4gf
+G6PA95F6GxTtbzIEZmTPVuzA/cfc9HU3HXDPqh+dySJ8/Ta4c4vX1lgeGGAvstNe
+q+9DaCGXs8MqMF8KtXNmOm3eS9q622hKEvTVEoxqj1t365kwKHaNpbObddQ6Xcny
+akvfh2L+8QbJSflcm8fL/JTup/2/cRG1ytOsaiXEr9JBEITOtQO0Ot/4Qzq+MJjv
+weaJ3hZ0c+cTy3tEvt+I7+lnW4Q5dB7aLR2/BZfLubhxz1SUVMuHfLH64fc0Uf1Q
+Nw==
+-----END CERTIFICATE-----
new file mode 100644
--- /dev/null
+++ b/dom/security/test/contentverifier/head.js
@@ -0,0 +1,210 @@
+/*
+ * Test Content-Signature for remote about:newtab
+ *  - Bug 1226928 - allow about:newtab to load remote content
+ *
+ * This tests content-signature verification on remote about:newtab in the
+ * following cases (see TESTS, all failed loads display about:blank fallback):
+ * - good case (signature should verify and correct page is displayed)
+ * - reload of newtab when the siganture was invalidated after the last correct
+ *   load
+ * - malformed content-signature header
+ * - malformed keyid directive
+ * - malformed p384ecdsa directive
+ * - wrong signature (this is not a siganture for the delivered document)
+ * - invalid signature (this is not even a signature)
+ * - loading a file that doesn't fit the key or signature
+ * - cache poisoning (load a malicious remote page not in newtab, subsequent
+ *   newtab load has to load the fallback)
+ */
+
+const ABOUT_NEWTAB_URI = "about:newtab";
+
+const BASE = "https://example.com/browser/dom/security/test/contentverifier/file_contentserver.sjs?";
+const URI_GOOD = BASE + "sig=good&x5u=good&file=good&header=good";
+
+const INVALIDATE_FILE = BASE + "invalidateFile=yep";
+const VALIDATE_FILE = BASE + "validateFile=yep";
+
+const URI_HEADER_BASE = BASE + "sig=good&x5u=good&file=good&header=";
+const URI_ERROR_HEADER = URI_HEADER_BASE + "error";
+const URI_KEYERROR_HEADER = URI_HEADER_BASE + "errorInX5U";
+const URI_SIGERROR_HEADER = URI_HEADER_BASE + "errorInSignature";
+const URI_NO_HEADER = URI_HEADER_BASE + "noHeader";
+
+const URI_BAD_SIG = BASE + "sig=bad&x5u=good&file=good&header=good";
+const URI_BROKEN_SIG = BASE + "sig=broken&x5u=good&file=good&header=good";
+const URI_BAD_X5U = BASE + "sig=good&x5u=bad&file=good&header=good";
+const URI_HTTP_X5U = BASE + "sig=good&x5u=http&file=good&header=good";
+const URI_BAD_FILE = BASE + "sig=good&x5u=good&file=bad&header=good";
+const URI_BAD_ALL = BASE + "sig=bad&x5u=bad&file=bad&header=bad";
+const URI_BAD_CSP = BASE + "sig=bad-csp&x5u=good&file=bad-csp&header=good";
+
+const URI_BAD_FILE_CACHED = BASE + "sig=good&x5u=good&file=bad&header=good&cached=true";
+
+const GOOD_ABOUT_STRING = "Just a fully good testpage for Bug 1226928";
+const BAD_ABOUT_STRING = "Just a bad testpage for Bug 1226928";
+const ABOUT_BLANK = "<head></head><body></body>";
+
+const URI_CLEANUP = BASE + "cleanup=true";
+const CLEANUP_DONE = "Done";
+
+const URI_SRI = BASE + "sig=sri&x5u=good&file=sri&header=good";
+const STYLESHEET_WITHOUT_SRI_BLOCKED = "Stylesheet without SRI blocked";
+const STYLESHEET_WITH_SRI_BLOCKED = "Stylesheet with SRI blocked";
+const STYLESHEET_WITH_SRI_LOADED = "Stylesheet with SRI loaded";
+const SCRIPT_WITHOUT_SRI_BLOCKED = "Script without SRI blocked";
+const SCRIPT_WITH_SRI_BLOCKED = "Script with SRI blocked";
+const SCRIPT_WITH_SRI_LOADED = "Script with SRI loaded";
+
+const CSP_TEST_SUCCESS_STRING = "CSP violation test succeeded.";
+
+// Needs to sync with pref "security.signed_content.CSP.default".
+const SIGNED_CONTENT_CSP = `{"csp-policies":[{"report-only":false,"script-src":["https://example.com","'unsafe-inline'"],"style-src":["https://example.com"]}]}`;
+
+var browser = null;
+var aboutNewTabService = Cc["@mozilla.org/browser/aboutnewtab-service;1"]
+                           .getService(Ci.nsIAboutNewTabService);
+
+function pushPrefs(...aPrefs) {
+  return new Promise((resolve) => {
+    SpecialPowers.pushPrefEnv({"set": aPrefs}, resolve);
+  });
+}
+
+/*
+ * run tests with input from TESTS
+ */
+function doTest(aExpectedStrings, reload, aUrl, aNewTabPref) {
+  // set about:newtab location for this test if it's a newtab test
+  if (aNewTabPref) {
+    aboutNewTabService.newTabURL = aNewTabPref;
+  }
+
+  // set prefs
+  yield pushPrefs(
+      ["browser.newtabpage.remote.content-signing-test", true],
+      ["browser.newtabpage.remote", true],
+      ["security.content.signature.root_hash",
+       "65:AE:D8:1E:B5:12:AE:B0:6B:38:58:BC:7C:47:35:3D:D4:EA:25:F1:63:DA:08:BB:86:3A:2E:97:39:66:8F:55"]);
+
+  if (aNewTabPref === URI_BAD_CSP) {
+    // Use stricter CSP to test CSP violation.
+    yield pushPrefs(["security.signed_content.CSP.default", "script-src 'self'; style-src 'self'"]);
+  } else {
+    // Use weaker CSP to test normal content.
+    yield pushPrefs(["security.signed_content.CSP.default", "script-src 'self' 'unsafe-inline'; style-src 'self'"]);
+  }
+
+  // start the test
+  yield BrowserTestUtils.withNewTab({
+      gBrowser,
+      url: aUrl,
+    },
+    function * (browser) {
+      // check if everything's set correct for testing
+      ok(Services.prefs.getBoolPref(
+          "browser.newtabpage.remote.content-signing-test"),
+          "sanity check: remote newtab signing test should be used");
+      ok(Services.prefs.getBoolPref("browser.newtabpage.remote"),
+          "sanity check: remote newtab should be used");
+      // we only check this if we really do a newtab test
+      if (aNewTabPref) {
+        ok(aboutNewTabService.overridden,
+            "sanity check: default URL for about:newtab should be overriden");
+        is(aboutNewTabService.newTabURL, aNewTabPref,
+            "sanity check: default URL for about:newtab should return the new URL");
+      }
+
+      // Every valid remote newtab page must have built-in CSP.
+      let shouldHaveCSP = ((aUrl === ABOUT_NEWTAB_URI) &&
+                          (aNewTabPref === URI_GOOD || aNewTabPref === URI_SRI));
+
+      if (shouldHaveCSP) {
+        is(browser.contentDocument.nodePrincipal.cspJSON, SIGNED_CONTENT_CSP,
+           "Valid remote newtab page must have built-in CSP.");
+      }
+
+      yield ContentTask.spawn(
+          browser, aExpectedStrings, function * (aExpectedStrings) {
+            for (let expectedString of aExpectedStrings) {
+              ok(content.document.documentElement.innerHTML.includes(expectedString),
+                 "Expect the following value in the result\n" + expectedString +
+                 "\nand got " + content.document.documentElement.innerHTML);
+            }
+          });
+
+      // for good test cases we check if a reload fails if the remote page
+      // changed from valid to invalid in the meantime
+      if (reload) {
+        yield BrowserTestUtils.withNewTab({
+            gBrowser,
+            url: INVALIDATE_FILE,
+          },
+          function * (browser2) {
+            yield ContentTask.spawn(browser2, null, function * () {
+              ok(content.document.documentElement.innerHTML.includes("Done"),
+                 "Expect the following value in the result\n" + "Done" +
+                 "\nand got " + content.document.documentElement.innerHTML);
+            });
+          }
+        );
+
+        browser.reload();
+        yield BrowserTestUtils.browserLoaded(browser);
+
+        let expectedStrings = [ABOUT_BLANK];
+        if (aNewTabPref == URI_SRI) {
+          expectedStrings = [
+            STYLESHEET_WITHOUT_SRI_BLOCKED,
+            STYLESHEET_WITH_SRI_BLOCKED,
+            SCRIPT_WITHOUT_SRI_BLOCKED,
+            SCRIPT_WITH_SRI_BLOCKED
+          ];
+        }
+        yield ContentTask.spawn(browser, expectedStrings,
+          function * (expectedStrings) {
+            for (let expectedString of expectedStrings) {
+              ok(content.document.documentElement.innerHTML.includes(expectedString),
+                 "Expect the following value in the result\n" + expectedString +
+                 "\nand got " + content.document.documentElement.innerHTML);
+            }
+          }
+        );
+
+        yield BrowserTestUtils.withNewTab({
+            gBrowser,
+            url: VALIDATE_FILE,
+          },
+          function * (browser2) {
+            yield ContentTask.spawn(browser2, null, function * () {
+              ok(content.document.documentElement.innerHTML.includes("Done"),
+                 "Expect the following value in the result\n" + "Done" +
+                 "\nand got " + content.document.documentElement.innerHTML);
+              });
+          }
+        );
+      }
+    }
+  );
+}
+
+function runTests() {
+  // run tests from TESTS
+  for (let i = 0; i < TESTS.length; i++) {
+    let testCase = TESTS[i];
+    let url = "", aNewTabPref = "";
+    let reload = false;
+    var aExpectedStrings = testCase.testStrings;
+    if (testCase.aboutURI) {
+      url = ABOUT_NEWTAB_URI;
+      aNewTabPref = testCase.aboutURI;
+      if (aNewTabPref == URI_GOOD || aNewTabPref == URI_SRI) {
+        reload = true;
+      }
+    } else {
+      url = testCase.url;
+    }
+
+    yield doTest(aExpectedStrings, reload, url, aNewTabPref);
+  }
+}
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -1126,30 +1126,16 @@ nsHttpChannel::CallOnStartRequest()
         if (NS_ERROR_FILE_TOO_BIG == rv) {
           // Don't throw the entry away, we will need it later.
           LOG(("  entry too big"));
         } else {
           NS_ENSURE_SUCCESS(rv, rv);
         }
     }
 
-    // Check for a Content-Signature header and inject mediator if the header is
-    // requested and available.
-    // If requested (mLoadInfo->GetVerifySignedContent), but not present, or
-    // present but not valid, fail this channel and return
-    // NS_ERROR_INVALID_SIGNATURE to indicate a signature error and trigger a
-    // fallback load in nsDocShell.
-    if (!mCanceled) {
-        rv = ProcessContentSignatureHeader(mResponseHead);
-        if (NS_FAILED(rv)) {
-            LOG(("Content-signature verification failed.\n"));
-            return rv;
-        }
-    }
-
     LOG(("  calling mListener->OnStartRequest\n"));
     if (mListener) {
         MOZ_ASSERT(!mOnStartRequestCalled,
                    "We should not call OsStartRequest twice");
         rv = mListener->OnStartRequest(this, mListenerContext);
         mOnStartRequestCalled = true;
         if (NS_FAILED(rv))
             return rv;
@@ -1202,16 +1188,34 @@ nsHttpChannel::CallOnStartRequest()
                 if (NS_FAILED(rv)) return rv;
             }
         } else if (mApplicationCacheForWrite) {
             LOG(("offline cache is up to date, not updating"));
             CloseOfflineCacheEntry();
         }
     }
 
+    // Check for a Content-Signature header and inject mediator if the header is
+    // requested and available.
+    // If requested (mLoadInfo->GetVerifySignedContent), but not present, or
+    // present but not valid, fail this channel and return
+    // NS_ERROR_INVALID_SIGNATURE to indicate a signature error and trigger a
+    // fallback load in nsDocShell.
+    // Note that OnStartRequest has already been called on the target stream
+    // listener at this point. We have to add the listener here that late to
+    // ensure that it's the last listener and can thus block the load in
+    // OnStopRequest.
+    if (!mCanceled) {
+        rv = ProcessContentSignatureHeader(mResponseHead);
+        if (NS_FAILED(rv)) {
+            LOG(("Content-signature verification failed.\n"));
+            return rv;
+        }
+    }
+
     return NS_OK;
 }
 
 nsresult
 nsHttpChannel::ProcessFailedProxyConnect(uint32_t httpStatus)
 {
     // Failure to set up a proxy tunnel via CONNECT means one of the following:
     // 1) Proxy wants authorization, or forbids.
@@ -1523,18 +1527,18 @@ nsHttpChannel::ProcessContentSignatureHe
     if (!aResponseHead->HasContentType()) {
         NS_WARNING("Empty content type can get us in trouble when verifying "
                    "content signatures");
         return NS_ERROR_INVALID_SIGNATURE;
     }
     // create a new listener that meadiates the content
     RefPtr<ContentVerifier> contentVerifyingMediator =
       new ContentVerifier(mListener, mListenerContext);
-    rv = contentVerifyingMediator->Init(
-      NS_ConvertUTF8toUTF16(contentSignatureHeader));
+    rv = contentVerifyingMediator->Init(contentSignatureHeader, this,
+                                        mListenerContext);
     NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_SIGNATURE);
     mListener = contentVerifyingMediator;
 
     return NS_OK;
 }
 
 /**
  * Decide whether or not to send a security report and, if so, give the
--- a/security/manager/ssl/ContentSignatureVerifier.cpp
+++ b/security/manager/ssl/ContentSignatureVerifier.cpp
@@ -5,28 +5,36 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "ContentSignatureVerifier.h"
 
 #include "BRNameMatchingPolicy.h"
 #include "SharedCertVerifier.h"
 #include "cryptohi.h"
 #include "keyhi.h"
+#include "mozilla/Assertions.h"
 #include "mozilla/Casting.h"
 #include "nsCOMPtr.h"
+#include "nsContentUtils.h"
+#include "nsISupportsPriority.h"
+#include "nsIURI.h"
 #include "nsNSSComponent.h"
 #include "nsSecurityHeaderParser.h"
+#include "nsStreamUtils.h"
 #include "nsWhitespaceTokenizer.h"
 #include "nsXPCOMStrings.h"
 #include "nssb64.h"
 #include "pkix/pkix.h"
 #include "pkix/pkixtypes.h"
 #include "secerr.h"
 
-NS_IMPL_ISUPPORTS(ContentSignatureVerifier, nsIContentSignatureVerifier)
+NS_IMPL_ISUPPORTS(ContentSignatureVerifier,
+                  nsIContentSignatureVerifier,
+                  nsIInterfaceRequestor,
+                  nsIStreamListener)
 
 using namespace mozilla;
 using namespace mozilla::pkix;
 using namespace mozilla::psm;
 
 static LazyLogModule gCSVerifierPRLog("ContentSignatureVerifier");
 #define CSVerifier_LOG(args) MOZ_LOG(gCSVerifierPRLog, LogLevel::Debug, args)
 
@@ -121,37 +129,28 @@ ReadChainIntoCertList(const nsACString& 
   if (inBlock || !certFound) {
     // the PEM data did not end; bad data.
     CSVerifier_LOG(("CSVerifier: supplied chain contains bad data\n"));
     return NS_ERROR_FAILURE;
   }
   return NS_OK;
 }
 
-// Create a context for a content signature verification.
-// It sets signature, certificate chain and name that should be used to verify
-// the data. The data parameter is the first part of the data to verify (this
-// can be the empty string).
-NS_IMETHODIMP
-ContentSignatureVerifier::CreateContext(const nsACString& aData,
-                                        const nsACString& aCSHeader,
-                                        const nsACString& aCertChain,
-                                        const nsACString& aName)
+nsresult
+ContentSignatureVerifier::CreateContextInternal(const nsACString& aData,
+                                                const nsACString& aCertChain,
+                                                const nsACString& aName)
 {
-  MutexAutoLock lock(mMutex);
+  MOZ_ASSERT(NS_IsMainThread());
   nsNSSShutDownPreventionLock locker;
   if (isAlreadyShutDown()) {
     CSVerifier_LOG(("CSVerifier: nss is already shutdown\n"));
     return NS_ERROR_FAILURE;
   }
 
-  if (mCx) {
-    return NS_ERROR_ALREADY_INITIALIZED;
-  }
-
   UniqueCERTCertList certCertList(CERT_NewCertList());
   if (!certCertList) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
   nsresult rv = ReadChainIntoCertList(aCertChain, certCertList.get(), locker);
   if (NS_FAILED(rv)) {
     return rv;
@@ -209,22 +208,16 @@ ContentSignatureVerifier::CreateContext(
   mKey.reset(CERT_ExtractPublicKey(node->cert));
 
   // in case we were not able to extract a key
   if (!mKey) {
     CSVerifier_LOG(("CSVerifier: unable to extract a key\n"));
     return NS_ERROR_INVALID_SIGNATURE;
   }
 
-  // we get the raw content-signature header here, so first parse aCSHeader
-  rv = ParseContentSignatureHeader(aCSHeader);
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-
   // Base 64 decode the signature
   UniqueSECItem rawSignatureItem(::SECITEM_AllocItem(nullptr, nullptr, 0));
   if (!rawSignatureItem ||
       !NSSBase64_DecodeBuffer(nullptr, rawSignatureItem.get(),
                               mSignature.get(), mSignature.Length())) {
     CSVerifier_LOG(("CSVerifier: decoding the signature failed\n"));
     return NS_ERROR_FAILURE;
   }
@@ -256,78 +249,197 @@ ContentSignatureVerifier::CreateContext(
   if (!mCx) {
     return NS_ERROR_INVALID_SIGNATURE;
   }
 
   if (VFY_Begin(mCx.get()) != SECSuccess) {
     return NS_ERROR_INVALID_SIGNATURE;
   }
 
-  rv = UpdateInternal(kPREFIX, lock, locker);
+  rv = UpdateInternal(kPREFIX, locker);
   if (NS_FAILED(rv)) {
     return rv;
   }
   // add data if we got any
-  return UpdateInternal(aData, lock, locker);
+  return UpdateInternal(aData, locker);
 }
 
 nsresult
-ContentSignatureVerifier::UpdateInternal(const nsACString& aData,
-  MutexAutoLock& /*proofOfLock*/,
-  const nsNSSShutDownPreventionLock& /*proofOfLock*/)
+ContentSignatureVerifier::DownloadCertChain()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (mCertChainURL.IsEmpty()) {
+    return NS_ERROR_INVALID_SIGNATURE;
+  }
+
+  nsCOMPtr<nsIURI> certChainURI;
+  nsresult rv = NS_NewURI(getter_AddRefs(certChainURI), mCertChainURL);
+  if (NS_FAILED(rv) || !certChainURI) {
+    return rv;
+  }
+
+  // If the address is not https, fail.
+  bool isHttps = false;
+  rv = certChainURI->SchemeIs("https", &isHttps);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  if (!isHttps) {
+    return NS_ERROR_INVALID_SIGNATURE;
+  }
+
+  rv = NS_NewChannel(getter_AddRefs(mChannel), certChainURI,
+                     nsContentUtils::GetSystemPrincipal(),
+                     nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+                     nsIContentPolicy::TYPE_OTHER);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  // we need this chain soon
+  nsCOMPtr<nsISupportsPriority> priorityChannel = do_QueryInterface(mChannel);
+  if (priorityChannel) {
+    priorityChannel->AdjustPriority(nsISupportsPriority::PRIORITY_HIGHEST);
+  }
+
+  rv = mChannel->AsyncOpen2(this);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+// Create a context for content signature verification using CreateContext below.
+// This function doesn't require a cert chain to be passed, but instead aCSHeader
+// must contain an x5u value that is then used to download the cert chain.
+NS_IMETHODIMP
+ContentSignatureVerifier::CreateContextWithoutCertChain(
+  nsIContentSignatureReceiverCallback *aCallback, const nsACString& aCSHeader,
+  const nsACString& aName)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  if (mInitialised) {
+    return NS_ERROR_ALREADY_INITIALIZED;
+  }
+  mInitialised = true;
+
+  // we get the raw content-signature header here, so first parse aCSHeader
+  nsresult rv = ParseContentSignatureHeader(aCSHeader);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  mCallback = aCallback;
+  mName.Assign(aName);
+
+  // We must download the cert chain now.
+  // This is async and blocks createContextInternal calls.
+  return DownloadCertChain();
+}
+
+// Create a context for a content signature verification.
+// It sets signature, certificate chain and name that should be used to verify
+// the data. The data parameter is the first part of the data to verify (this
+// can be the empty string).
+NS_IMETHODIMP
+ContentSignatureVerifier::CreateContext(const nsACString& aData,
+                                        const nsACString& aCSHeader,
+                                        const nsACString& aCertChain,
+                                        const nsACString& aName)
+{
+  if (mInitialised) {
+    return NS_ERROR_ALREADY_INITIALIZED;
+  }
+  mInitialised = true;
+  // The cert chain is given in aCertChain so we don't have to download anything.
+  mHasCertChain = true;
+
+  // we get the raw content-signature header here, so first parse aCSHeader
+  nsresult rv = ParseContentSignatureHeader(aCSHeader);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  return CreateContextInternal(aData, aCertChain, aName);
+}
+
+nsresult
+ContentSignatureVerifier::UpdateInternal(
+  const nsACString& aData, const nsNSSShutDownPreventionLock& /*proofOfLock*/)
 {
   if (!aData.IsEmpty()) {
     if (VFY_Update(mCx.get(), (const unsigned char*)nsPromiseFlatCString(aData).get(),
                    aData.Length()) != SECSuccess){
       return NS_ERROR_INVALID_SIGNATURE;
     }
   }
   return NS_OK;
 }
 
 /**
  * Add data to the context that shold be verified.
  */
 NS_IMETHODIMP
 ContentSignatureVerifier::Update(const nsACString& aData)
 {
+  MOZ_ASSERT(NS_IsMainThread());
   nsNSSShutDownPreventionLock locker;
   if (isAlreadyShutDown()) {
     CSVerifier_LOG(("CSVerifier: nss is already shutdown\n"));
     return NS_ERROR_FAILURE;
   }
-  MutexAutoLock lock(mMutex);
-  return UpdateInternal(aData, lock, locker);
+
+  // If we didn't create the context yet, bail!
+  if (!mHasCertChain) {
+    MOZ_ASSERT_UNREACHABLE(
+      "Someone called ContentSignatureVerifier::Update before "
+      "downloading the cert chain.");
+    return NS_ERROR_FAILURE;
+  }
+
+  return UpdateInternal(aData, locker);
 }
 
 /**
  * Finish signature verification and return the result in _retval.
  */
 NS_IMETHODIMP
 ContentSignatureVerifier::End(bool* _retval)
 {
   NS_ENSURE_ARG(_retval);
-  MutexAutoLock lock(mMutex);
+  MOZ_ASSERT(NS_IsMainThread());
   nsNSSShutDownPreventionLock locker;
   if (isAlreadyShutDown()) {
     CSVerifier_LOG(("CSVerifier: nss is already shutdown\n"));
     return NS_ERROR_FAILURE;
   }
 
+  // If we didn't create the context yet, bail!
+  if (!mHasCertChain) {
+    MOZ_ASSERT_UNREACHABLE(
+      "Someone called ContentSignatureVerifier::End before "
+      "downloading the cert chain.");
+    return NS_ERROR_FAILURE;
+  }
+
   *_retval = (VFY_End(mCx.get()) == SECSuccess);
 
   return NS_OK;
 }
 
 nsresult
 ContentSignatureVerifier::ParseContentSignatureHeader(
   const nsACString& aContentSignatureHeader)
 {
+  MOZ_ASSERT(NS_IsMainThread());
   // We only support p384 ecdsa according to spec
   NS_NAMED_LITERAL_CSTRING(signature_var, "p384ecdsa");
+  NS_NAMED_LITERAL_CSTRING(certChainURL_var, "x5u");
 
   nsSecurityHeaderParser parser(aContentSignatureHeader.BeginReading());
   nsresult rv = parser.Parse();
   if (NS_FAILED(rv)) {
     CSVerifier_LOG(("CSVerifier: could not parse ContentSignature header\n"));
     return NS_ERROR_FAILURE;
   }
   LinkedList<nsSecurityHeaderDirective>* directives = parser.GetDirectives();
@@ -341,24 +453,113 @@ ContentSignatureVerifier::ParseContentSi
       if (!mSignature.IsEmpty()) {
         CSVerifier_LOG(("CSVerifier: found two ContentSignatures\n"));
         return NS_ERROR_INVALID_SIGNATURE;
       }
 
       CSVerifier_LOG(("CSVerifier: found a ContentSignature directive\n"));
       mSignature = directive->mValue;
     }
+    if (directive->mName.Length() == certChainURL_var.Length() &&
+        directive->mName.EqualsIgnoreCase(certChainURL_var.get(),
+                                          certChainURL_var.Length())) {
+      if (!mCertChainURL.IsEmpty()) {
+        CSVerifier_LOG(("CSVerifier: found two x5u values\n"));
+        return NS_ERROR_INVALID_SIGNATURE;
+      }
+
+      CSVerifier_LOG(("CSVerifier: found an x5u directive\n"));
+      mCertChainURL = directive->mValue;
+    }
   }
 
   // we have to ensure that we found a signature at this point
   if (mSignature.IsEmpty()) {
     CSVerifier_LOG(("CSVerifier: got a Content-Signature header but didn't find a signature.\n"));
     return NS_ERROR_FAILURE;
   }
 
   // Bug 769521: We have to change b64 url to regular encoding as long as we
   // don't have a b64 url decoder. This should change soon, but in the meantime
   // we have to live with this.
   mSignature.ReplaceChar('-', '+');
   mSignature.ReplaceChar('_', '/');
 
   return NS_OK;
 }
+
+/* nsIStreamListener implementation */
+
+NS_IMETHODIMP
+ContentSignatureVerifier::OnStartRequest(nsIRequest* aRequest,
+                                         nsISupports* aContext)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+ContentSignatureVerifier::OnStopRequest(nsIRequest* aRequest,
+                                        nsISupports* aContext, nsresult aStatus)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  nsCOMPtr<nsIContentSignatureReceiverCallback> callback;
+  callback.swap(mCallback);
+  nsresult rv;
+
+  // Check HTTP status code and return if it's not 200.
+  nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aRequest, &rv);
+  uint32_t httpResponseCode;
+  if (NS_FAILED(rv) || NS_FAILED(http->GetResponseStatus(&httpResponseCode)) ||
+      httpResponseCode != 200) {
+    callback->ContextCreated(false);
+    return NS_OK;
+  }
+
+  if (NS_FAILED(aStatus)) {
+    callback->ContextCreated(false);
+    return NS_OK;
+  }
+
+  nsAutoCString certChain;
+  for (uint32_t i = 0; i < mCertChain.Length(); ++i) {
+    certChain.Append(mCertChain[i]);
+  }
+
+  // We got the cert chain now. Let's create the context.
+  rv = CreateContextInternal(NS_LITERAL_CSTRING(""), certChain, mName);
+  if (NS_FAILED(rv)) {
+    callback->ContextCreated(false);
+    return NS_OK;
+  }
+
+  mHasCertChain = true;
+  callback->ContextCreated(true);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+ContentSignatureVerifier::OnDataAvailable(nsIRequest* aRequest,
+                                          nsISupports* aContext,
+                                          nsIInputStream* aInputStream,
+                                          uint64_t aOffset, uint32_t aCount)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  nsAutoCString buffer;
+
+  nsresult rv = NS_ConsumeStream(aInputStream, aCount, buffer);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  if (!mCertChain.AppendElement(buffer, fallible)) {
+    mCertChain.TruncateLength(0);
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+ContentSignatureVerifier::GetInterface(const nsIID& uuid, void** result)
+{
+  return QueryInterface(uuid, result);
+}
--- a/security/manager/ssl/ContentSignatureVerifier.h
+++ b/security/manager/ssl/ContentSignatureVerifier.h
@@ -6,62 +6,87 @@
 
 
 #ifndef ContentSignatureVerifier_h
 #define ContentSignatureVerifier_h
 
 #include "cert.h"
 #include "CSTrustDomain.h"
 #include "nsIContentSignatureVerifier.h"
+#include "nsIStreamListener.h"
 #include "nsNSSShutDown.h"
 #include "ScopedNSSTypes.h"
 
 // 45a5fe2f-c350-4b86-962d-02d5aaaa955a
 #define NS_CONTENTSIGNATUREVERIFIER_CID \
   { 0x45a5fe2f, 0xc350, 0x4b86, \
     { 0x96, 0x2d, 0x02, 0xd5, 0xaa, 0xaa, 0x95, 0x5a } }
 #define NS_CONTENTSIGNATUREVERIFIER_CONTRACTID \
     "@mozilla.org/security/contentsignatureverifier;1"
 
 class ContentSignatureVerifier final : public nsIContentSignatureVerifier
+                                     , public nsIStreamListener
                                      , public nsNSSShutDownObject
+                                     , public nsIInterfaceRequestor
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSICONTENTSIGNATUREVERIFIER
+  NS_DECL_NSIINTERFACEREQUESTOR
+  NS_DECL_NSISTREAMLISTENER
+  NS_DECL_NSIREQUESTOBSERVER
 
   ContentSignatureVerifier()
     : mCx(nullptr)
-    , mMutex("CSVerifier::mMutex")
+    , mInitialised(false)
+    , mHasCertChain(false)
   {
   }
 
   // nsNSSShutDownObject
   virtual void virtualDestroyNSSReference() override
   {
     destructorSafeDestroyNSSReference();
   }
 
 private:
   ~ContentSignatureVerifier();
 
   nsresult UpdateInternal(const nsACString& aData,
-                          MutexAutoLock& /*proofOfLock*/,
                           const nsNSSShutDownPreventionLock& /*proofOfLock*/);
+  nsresult DownloadCertChain();
+  nsresult CreateContextInternal(const nsACString& aData,
+                                 const nsACString& aCertChain,
+                                 const nsACString& aName);
 
   void destructorSafeDestroyNSSReference()
   {
     mCx = nullptr;
     mKey = nullptr;
   }
 
   nsresult ParseContentSignatureHeader(const nsACString& aContentSignatureHeader);
 
   // verifier context for incremental verifications
   mozilla::UniqueVFYContext mCx;
+  bool mInitialised;
+  // Indicates whether we hold a cert chain to verify the signature or not.
+  // It's set by default in CreateContext or when the channel created in
+  // DownloadCertChain finished. Update and End must only be called after
+  // mHashCertChain is set.
+  bool mHasCertChain;
   // signature to verify
   nsCString mSignature;
+  // x5u (X.509 URL) value pointing to pem cert chain
+  nsCString mCertChainURL;
+  // the downloaded cert chain to verify against
+  FallibleTArray<nsCString> mCertChain;
   // verification key
   mozilla::UniqueSECKEYPublicKey mKey;
-  mozilla::Mutex mMutex;
+  // name of the verifying context
+  nsCString mName;
+  // callback to notify when finished
+  nsCOMPtr<nsIContentSignatureReceiverCallback> mCallback;
+  // channel to download the cert chain
+  nsCOMPtr<nsIChannel> mChannel;
 };
 
 #endif // ContentSignatureVerifier_h
--- a/security/manager/ssl/nsIContentSignatureVerifier.idl
+++ b/security/manager/ssl/nsIContentSignatureVerifier.idl
@@ -1,15 +1,17 @@
 /* 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"
 
+interface nsIContentSignatureReceiverCallback;
+
 /**
  * An interface for verifying content-signatures, inspired by
  * https://tools.ietf.org/html/draft-thomson-http-content-signature-00
  * described here https://github.com/franziskuskiefer/content-signature/tree/pki
  *
  * A new signature verifier instance should be created for each signature
  * verification - you can create these instances with do_CreateInstance.
  *
@@ -33,44 +35,82 @@ interface nsIContentSignatureVerifier : 
    *                                url-safe base64 encoded.
    * @param aCertificateChain       The certificate chain to use for verification.
    *                                PEM encoded string.
    * @param aName                   The (host)name for which the end entity must
                                     be valid.
    * @returns true if the signature matches the data and aCertificateChain is
    *          valid within aContext, false if not.
    */
-  boolean verifyContentSignature(in ACString aData, in ACString aSignature,
+  boolean verifyContentSignature(in ACString aData,
+                                 in ACString aContentSignatureHeader,
                                  in ACString aCertificateChain,
                                  in ACString aName);
 
   /**
    * Creates a context to verify a content signature against data that is added
    * later with update calls.
    *
    * @param aData                   The first chunk of data to be tested.
    * @param aContentSignatureHeader The signature of the data, url-safe base64
    *                                encoded.
    * @param aCertificateChain       The certificate chain to use for
    *                                verification. PEM encoded string.
    * @param aName                   The (host)name for which the end entity must
                                     be valid.
    */
-  void createContext(in ACString aData, in ACString aSignature,
+  void createContext(in ACString aData, in ACString aContentSignatureHeader,
                      in ACString aCertificateChain, in ACString aName);
 
   /**
+   * Creates a context to verify a content signature against data that is added
+   * later with update calls.
+   * This does not require the caller to download the certificate chain. It's
+   * done internally.
+   * It requires the x5u parameter to be present in aContentSignatureHeader
+   *
+   * NOTE: Callers have to wait for aCallback to return before invoking anything
+   *       else. Otherwise the ContentSignatureVerifier will fail.
+   *
+   * @param aCallback               Callback that's invoked when the cert chain
+   *                                got fetched.
+   * @param aContentSignatureHeader The signature of the data, url-safe base64
+   *                                encoded, and the x5u value.
+   * @param aName                   The (host)name for which the end entity must
+                                    be valid.
+   */
+  void createContextWithoutCertChain(in nsIContentSignatureReceiverCallback aCallback,
+                                     in ACString aContentSignatureHeader,
+                                     in ACString aName);
+
+  /**
    * Adds data to the context that was used to generate the signature.
    *
    * @param aData        More data to be tested.
    */
   void update(in ACString aData);
 
   /**
    * Finalises the signature and returns the result of the signature
    * verification.
    *
    * @returns true if the signature matches the data added with createContext
    *          and update, false if not.
    */
   boolean end();
+};
 
+/**
+ * Callback for nsIContentSignatureVerifier.
+ * { 0x1eb90707, 0xdf59, 0x48b7, \
+ *   { 0x9d, 0x42, 0xd8, 0xbf, 0x63, 0x0a, 0xe7, 0x44 } }
+ */
+[scriptable, uuid(1eb90707-df59-48b7-9d42-d8bf630ae744)]
+interface nsIContentSignatureReceiverCallback : nsISupports
+{
+  /**
+   * Notification callback that's called by nsIContentSignatureVerifier when
+   * the cert chain is downloaded.
+   * If download and initialisation were successful, successful is true,
+   * otherwise false. If successful is false, the verification must be aborted.
+   */
+  void contextCreated(in boolean successful);
 };