Bug 1263793 - Using content signature verifier for verifying remote newtab, r=keeler,mayhemer draft
authorFranziskus Kiefer <franziskuskiefer@gmail.com>
Thu, 19 May 2016 10:59:48 +0200
changeset 387566 4a59d3aa82d7b3073bc7adce1e72534540adf765
parent 387427 5fd14a66be3191e5628af3cfb0a456dd70f179b8
child 525384 a81ce4173f6b9f1bf0ae96d4f26d7ef7c3030b9e
push id22999
push userfranziskuskiefer@gmail.com
push dateThu, 14 Jul 2016 09:23:58 +0000
reviewerskeeler, mayhemer
bugs1263793
milestone50.0a1
Bug 1263793 - Using content signature verifier for verifying remote newtab, r=keeler,mayhemer * Init gets request and context now. * Fixed some comments and nits. MozReview-Commit-ID: 163AvJAYL5n
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/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
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,228 @@
 /* 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.
+  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,
+                                       mOffset, 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"));
+  // Keep references to the request and context. We need them in FinishSignature
+  // and the ContextCreated callback.
+  mContentRequest = aRequest;
+  mContentContext = aContext;
 
-  // 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;
-    }
+  // Update the signature verifier if the context has been created.
+  if (mContextCreated) {
+    return mVerifier->Update(mContent.LastElement());
   }
 
-  // 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 offset
+  mOffset += aCount;
 
   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);
 
-  // Extract the public key from the keyItem
-  ScopedCERTSubjectPublicKeyInfo pki(
-    SECKEY_DecodeDERSubjectPublicKeyInfo(keyItem));
-  if (!pki) {
-    return NS_ERROR_INVALID_SIGNATURE;
-  }
-  aPublicKeyOut = SECKEY_ExtractPublicKey(pki.get());
+    // Make sure that OnStartRequest was called and we have a request.
+    MOZ_ASSERT(mContentRequest);
 
-  // in case we were not able to extract a key
-  if (!aPublicKeyOut) {
-    return NS_ERROR_INVALID_SIGNATURE;
-  }
+    // 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);
+      return nextListener->OnStopRequest(mContentRequest, mContentContext,
+                                         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,65 @@
 /* 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;
+  // bytes read in OnDataAvailable
+  uint64_t mOffset = 0;
 };
 
 #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,13 @@ 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
   script.js
   style.css
 
 [browser_verify_content_about_newtab.js]
--- a/dom/security/test/contentverifier/browser_verify_content_about_newtab.js
+++ b/dom/security/test/contentverifier/browser_verify_content_about_newtab.js
@@ -15,44 +15,45 @@
  * - 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 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&key=good&file=good&header=";
+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 + "errorInKeyid";
+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&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_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&key=good&file=bad&header=good&cached=true";
+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&key=good&file=sri&header=good";
+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.";
@@ -65,17 +66,18 @@ const TESTS = [
   //   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_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_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,
@@ -103,27 +105,19 @@ function doTest(aExpectedStrings, reload
   // 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"
-      ]);
+      ["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'"]);
   }
--- 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-----
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -1007,30 +1007,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;
@@ -1082,16 +1068,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.
@@ -1403,18 +1407,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);
 };