Bug 1226928 - signature verification for content-signing, r=keeler,mayhemer draft
authorFranziskus Kiefer <franziskuskiefer@gmail.com>
Tue, 09 Feb 2016 10:27:38 +0100
changeset 340470 664cd6cfef6edb38cd111d32be66f5ab93cf73f8
parent 340469 434a2900b5d878dbb0d77ee3f572beac7520e658
child 340471 5ed9fa808bcf3eb991fd9cf32e809ba4b8e18b4d
push id12972
push userjhao@mozilla.com
push dateTue, 15 Mar 2016 11:01:23 +0000
reviewerskeeler, mayhemer
bugs1226928
milestone48.0a1
Bug 1226928 - signature verification for content-signing, r=keeler,mayhemer MozReview-Commit-ID: 2p42HbDdKpx
dom/security/ContentVerifier.cpp
dom/security/ContentVerifier.h
dom/security/moz.build
security/manager/ssl/ScopedNSSTypes.h
security/manager/ssl/moz.build
xpcom/base/ErrorList.h
new file mode 100644
--- /dev/null
+++ b/dom/security/ContentVerifier.cpp
@@ -0,0 +1,375 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
+
+#include "ContentVerifier.h"
+
+#include "mozilla/fallible.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPtr.h"
+#include "nsCharSeparatedTokenizer.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);
+
+nsresult
+ContentVerifier::Init(const nsAString& aContentSignatureHeader)
+{
+  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"));
+    return NS_ERROR_INVALID_SIGNATURE;
+  }
+
+  nsresult rv = ParseContentSignatureHeader(aContentSignatureHeader);
+  NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_SIGNATURE);
+  return CreateContext();
+}
+
+/**
+ * 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);
+  if (!decodedData->AppendElement(segment, fallible)) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+  *outWrittenCount = aCount;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+ContentVerifier::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
+{
+  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;
+      }
+    }
+  }
+
+  // propagate OnStopRequest and return
+  return mNextListener->OnStopRequest(aRequest, aContext, rv);
+}
+
+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;
+  }
+
+  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&)
+{
+  // 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;
+  }
+
+  // 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 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;
+  }
+
+  // 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;
+  }
+
+  // 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;
+    }
+  }
+
+  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
new file mode 100644
--- /dev/null
+++ b/dom/security/ContentVerifier.h
@@ -0,0 +1,98 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
+
+#ifndef mozilla_dom_ContentVerifier_h
+#define mozilla_dom_ContentVerifier_h
+
+#include "nsCOMPtr.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:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSISTREAMLISTENER
+  NS_DECL_NSIREQUESTOBSERVER
+
+  explicit ContentVerifier(nsIStreamListener* aMediatedListener,
+                           nsISupports* aMediatedContext)
+    : mNextListener(aMediatedListener)
+    , mContext(aMediatedContext)
+    , mCx(nullptr) {}
+
+  nsresult Init(const nsAString& aContentSignatureHeader);
+
+  // nsNSSShutDownObject
+  virtual void virtualDestroyNSSReference() override
+  {
+    destructorSafeDestroyNSSReference();
+  }
+
+protected:
+  virtual ~ContentVerifier()
+  {
+    nsNSSShutDownPreventionLock locker;
+    if (isAlreadyShutDown()) {
+      return;
+    }
+    destructorSafeDestroyNSSReference();
+    shutdown(calledFromObject);
+  }
+
+  void destructorSafeDestroyNSSReference()
+  {
+    mCx = nullptr;
+  }
+
+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&);
+
+  // 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);
+
+  // 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;
+};
+
+#endif /* mozilla_dom_ContentVerifier_h */
--- a/dom/security/moz.build
+++ b/dom/security/moz.build
@@ -2,30 +2,32 @@
 # vim: set filetype=python:
 # 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/.
 
 TEST_DIRS += ['test']
 
 EXPORTS.mozilla.dom += [
+    'ContentVerifier.h',
     'nsContentSecurityManager.h',
     'nsCSPContext.h',
     'nsCSPService.h',
     'nsCSPUtils.h',
     'nsMixedContentBlocker.h',
     'SRICheck.h',
     'SRIMetadata.h',
 ]
 
 EXPORTS += [
     'nsContentSecurityManager.h',
 ]
 
 UNIFIED_SOURCES += [
+    'ContentVerifier.cpp',
     'nsContentSecurityManager.cpp',
     'nsCSPContext.cpp',
     'nsCSPParser.cpp',
     'nsCSPService.cpp',
     'nsCSPUtils.cpp',
     'nsMixedContentBlocker.cpp',
     'SRICheck.cpp',
     'SRIMetadata.cpp',
--- a/security/manager/ssl/ScopedNSSTypes.h
+++ b/security/manager/ssl/ScopedNSSTypes.h
@@ -289,16 +289,21 @@ inline void SECOID_DestroyAlgorithmID_tr
   return SECOID_DestroyAlgorithmID(a, true);
 }
 
 inline void SECKEYEncryptedPrivateKeyInfo_true(SECKEYEncryptedPrivateKeyInfo * epki)
 {
   return SECKEY_DestroyEncryptedPrivateKeyInfo(epki, PR_TRUE);
 }
 
+inline void VFY_DestroyContext_true(VFYContext * ctx)
+{
+  VFY_DestroyContext(ctx, true);
+}
+
 } // namespace internal
 
 // Deprecated: use the equivalent UniquePtr templates instead.
 MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedSECItem,
                                           SECItem,
                                           internal::SECITEM_FreeItem_true)
 MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedSECKEYPrivateKey,
                                           SECKEYPrivateKey,
@@ -356,11 +361,14 @@ MOZ_TYPE_SPECIFIC_UNIQUE_PTR_TEMPLATE(Un
                                       SECItem,
                                       internal::SECITEM_FreeItem_true)
 MOZ_TYPE_SPECIFIC_UNIQUE_PTR_TEMPLATE(UniqueSECKEYPublicKey,
                                       SECKEYPublicKey,
                                       SECKEY_DestroyPublicKey)
 MOZ_TYPE_SPECIFIC_UNIQUE_PTR_TEMPLATE(UniqueSECMODModule,
                                       SECMODModule,
                                       SECMOD_DestroyModule)
+MOZ_TYPE_SPECIFIC_UNIQUE_PTR_TEMPLATE(UniqueVFYContext,
+                                      VFYContext,
+                                      internal::VFY_DestroyContext_true)
 } // namespace mozilla
 
 #endif // mozilla_ScopedNSSTypes_h
--- a/security/manager/ssl/moz.build
+++ b/security/manager/ssl/moz.build
@@ -56,16 +56,17 @@ EXPORTS += [
     'nsClientAuthRemember.h',
     'nsCrypto.h',
     'nsNSSCallbacks.h',
     'nsNSSCertificate.h',
     'nsNSSComponent.h',
     'nsNSSHelper.h',
     'nsNSSShutDown.h',
     'nsRandomGenerator.h',
+    'nsSecurityHeaderParser.h',
     'NSSErrorsService.h',
     'ScopedNSSTypes.h',
     'SharedCertVerifier.h',
 ]
 
 EXPORTS.mozilla += [
     'DataStorage.h',
     'PublicSSL.h',
--- a/xpcom/base/ErrorList.h
+++ b/xpcom/base/ErrorList.h
@@ -174,16 +174,20 @@
   ERROR(NS_ERROR_UNKNOWN_PROTOCOL,                    FAILURE(18)),
   /* The content encoding of the source document was incorrect, for example
    * returning a plain HTML document advertised as Content-Encoding: gzip */
   ERROR(NS_ERROR_INVALID_CONTENT_ENCODING,            FAILURE(27)),
   /* A transport level corruption was found in the source document. for example
    * a document with a calculated checksum that does not match the Content-MD5
    * http header. */
   ERROR(NS_ERROR_CORRUPTED_CONTENT,                   FAILURE(29)),
+  /* A content signature verification failed for some reason. This can be either
+   * an actual verification error, or any other error that led to the fact that
+   * a content signature that was expected couldn't be verified. */
+  ERROR(NS_ERROR_INVALID_SIGNATURE,                   FAILURE(58)),
   /* While parsing for the first component of a header field using syntax as in
    * Content-Disposition or Content-Type, the first component was found to be
    * empty, such as in: Content-Disposition: ; filename=foo */
   ERROR(NS_ERROR_FIRST_HEADER_FIELD_COMPONENT_EMPTY,  FAILURE(34)),
   /* Returned from nsIChannel::asyncOpen when trying to open the channel again
    * (reopening is not supported). */
   ERROR(NS_ERROR_ALREADY_OPENED,                      FAILURE(73)),