Bug 896620: Make marketplace certs work on in all products, r=keeler
authorBrian Smith <brian@briansmith.org>
Fri, 14 Feb 2014 14:37:07 -0800
changeset 168929 8cdaaf3da9f84ba309429aee968d2f55a905c63d
parent 168928 5d8fc2d51ec217853af4e817905584a4b90992d3
child 168930 b80f7eece91393dd47d60d480e9dd06e014d0fc2
push id39842
push userphilringnalda@gmail.com
push dateSat, 15 Feb 2014 18:16:57 +0000
treeherdermozilla-inbound@e8afde67c290 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskeeler
bugs896620
milestone30.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 896620: Make marketplace certs work on in all products, r=keeler
dom/apps/src/Webapps.jsm
security/apps/AppSignatureVerification.cpp
security/apps/AppTrustDomain.cpp
security/apps/AppTrustDomain.h
security/apps/Makefile.in
security/apps/gen_cert_header.py
security/apps/marketplace-dev-public.crt
security/apps/marketplace-dev-reviewers.crt
security/apps/marketplace-prod-public.crt
security/apps/marketplace-prod-reviewers.crt
security/apps/moz.build
security/build/b2g-app-root-cert.der
security/certverifier/NSSCertDBTrustDomain.cpp
security/manager/ssl/public/nsIX509CertDB.idl
security/manager/ssl/src/JARSignatureVerification.cpp
security/manager/ssl/src/moz.build
security/manager/ssl/src/nsCrypto.cpp
security/manager/ssl/tests/unit/head_psm.js
security/manager/ssl/tests/unit/test_signed_apps-marketplace.js
security/manager/ssl/tests/unit/test_signed_apps.js
toolkit/toolkit.mozbuild
--- a/dom/apps/src/Webapps.jsm
+++ b/dom/apps/src/Webapps.jsm
@@ -3081,18 +3081,18 @@ onInstallSuccessAck: function onInstallS
       return [zipReader, isSigned];
 
     }).bind(this));
   },
 
   _openSignedPackage: function(aZipFile, aCertDb) {
     let deferred = Promise.defer();
 
-    aCertDb.openSignedJARFileAsync(
-       aZipFile,
+    aCertDb.openSignedAppFileAsync(
+       Ci.nsIX509CertDB.AppMarketplaceProdPublicRoot, aZipFile,
        function(aRv, aZipReader) {
          deferred.resolve([aRv, aZipReader]);
        }
     );
 
     return deferred.promise;
   },
 
new file mode 100644
--- /dev/null
+++ b/security/apps/AppSignatureVerification.cpp
@@ -0,0 +1,843 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=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/. */
+
+#ifdef MOZ_LOGGING
+#define FORCE_PR_LOG 1
+#endif
+
+#include "nsNSSCertificateDB.h"
+
+#include "insanity/pkix.h"
+#include "mozilla/RefPtr.h"
+#include "CryptoTask.h"
+#include "AppTrustDomain.h"
+#include "nsComponentManagerUtils.h"
+#include "nsCOMPtr.h"
+#include "nsHashKeys.h"
+#include "nsIFile.h"
+#include "nsIInputStream.h"
+#include "nsIStringEnumerator.h"
+#include "nsIZipReader.h"
+#include "nsNSSCertificate.h"
+#include "nsProxyRelease.h"
+#include "nsString.h"
+#include "nsTHashtable.h"
+#include "ScopedNSSTypes.h"
+
+#include "base64.h"
+#include "certdb.h"
+#include "secmime.h"
+#include "plstr.h"
+#include "prlog.h"
+
+using namespace insanity::pkix;
+using namespace mozilla;
+using namespace mozilla::psm;
+
+#ifdef PR_LOGGING
+extern PRLogModuleInfo* gPIPNSSLog;
+#endif
+
+namespace {
+
+// Finds exactly one (signature metadata) entry that matches the given
+// search pattern, and then load it. Fails if there are no matches or if
+// there is more than one match. If bugDigest is not null then on success
+// bufDigest will contain the SHA-1 digeset of the entry.
+nsresult
+FindAndLoadOneEntry(nsIZipReader * zip,
+                    const nsACString & searchPattern,
+                    /*out*/ nsACString & filename,
+                    /*out*/ SECItem & buf,
+                    /*optional, out*/ Digest * bufDigest)
+{
+  nsCOMPtr<nsIUTF8StringEnumerator> files;
+  nsresult rv = zip->FindEntries(searchPattern, getter_AddRefs(files));
+  if (NS_FAILED(rv) || !files) {
+    return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
+  }
+
+  bool more;
+  rv = files->HasMore(&more);
+  NS_ENSURE_SUCCESS(rv, rv);
+  if (!more) {
+    return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
+  }
+
+  rv = files->GetNext(filename);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Check if there is more than one match, if so then error!
+  rv = files->HasMore(&more);
+  NS_ENSURE_SUCCESS(rv, rv);
+  if (more) {
+    return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
+  }
+
+  nsCOMPtr<nsIInputStream> stream;
+  rv = zip->GetInputStream(filename, getter_AddRefs(stream));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // The size returned by Available() might be inaccurate so we need to check
+  // that Available() matches up with the actual length of the file.
+  uint64_t len64;
+  rv = stream->Available(&len64);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+
+  // Cap the maximum accepted size of signature-related files at 1MB (which is
+  // still crazily huge) to avoid OOM. The uncompressed length of an entry can be
+  // hundreds of times larger than the compressed version, especially if
+  // someone has speifically crafted the entry to cause OOM or to consume
+  // massive amounts of disk space.
+  //
+  // Also, keep in mind bug 164695 and that we must leave room for
+  // null-terminating the buffer.
+  static const uint32_t MAX_LENGTH = 1024 * 1024;
+  static_assert(MAX_LENGTH < UINT32_MAX, "MAX_LENGTH < UINT32_MAX");
+  NS_ENSURE_TRUE(len64 < MAX_LENGTH, NS_ERROR_FILE_CORRUPTED);
+  NS_ENSURE_TRUE(len64 < UINT32_MAX, NS_ERROR_FILE_CORRUPTED); // bug 164695
+  SECITEM_AllocItem(buf, static_cast<uint32_t>(len64 + 1));
+
+  // buf.len == len64 + 1. We attempt to read len64 + 1 bytes instead of len64,
+  // so that we can check whether the metadata in the ZIP for the entry is
+  // incorrect.
+  uint32_t bytesRead;
+  rv = stream->Read(char_ptr_cast(buf.data), buf.len, &bytesRead);
+  NS_ENSURE_SUCCESS(rv, rv);
+  if (bytesRead != len64) {
+    return NS_ERROR_SIGNED_JAR_ENTRY_INVALID;
+  }
+
+  buf.data[buf.len - 1] = 0; // null-terminate
+
+  if (bufDigest) {
+    rv = bufDigest->DigestBuf(SEC_OID_SHA1, buf.data, buf.len - 1);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  return NS_OK;
+}
+
+// Verify the digest of an entry. We avoid loading the entire entry into memory
+// at once, which would require memory in proportion to the size of the largest
+// entry. Instead, we require only a small, fixed amount of memory.
+//
+// @param digestFromManifest The digest that we're supposed to check the file's
+//                           contents against, from the manifest
+// @param buf A scratch buffer that we use for doing the I/O, which must have
+//            already been allocated. The size of this buffer is the unit
+//            size of our I/O.
+nsresult
+VerifyEntryContentDigest(nsIZipReader * zip, const nsACString & aFilename,
+                         const SECItem & digestFromManifest, SECItem & buf)
+{
+  MOZ_ASSERT(buf.len > 0);
+  if (digestFromManifest.len != SHA1_LENGTH)
+    return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
+
+  nsresult rv;
+
+  nsCOMPtr<nsIInputStream> stream;
+  rv = zip->GetInputStream(aFilename, getter_AddRefs(stream));
+  if (NS_FAILED(rv)) {
+    return NS_ERROR_SIGNED_JAR_ENTRY_MISSING;
+  }
+
+  uint64_t len64;
+  rv = stream->Available(&len64);
+  NS_ENSURE_SUCCESS(rv, rv);
+  if (len64 > UINT32_MAX) {
+    return NS_ERROR_SIGNED_JAR_ENTRY_TOO_LARGE;
+  }
+
+  ScopedPK11Context digestContext(PK11_CreateDigestContext(SEC_OID_SHA1));
+  if (!digestContext) {
+    return PRErrorCode_to_nsresult(PR_GetError());
+  }
+
+  rv = MapSECStatus(PK11_DigestBegin(digestContext));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  uint64_t totalBytesRead = 0;
+  for (;;) {
+    uint32_t bytesRead;
+    rv = stream->Read(char_ptr_cast(buf.data), buf.len, &bytesRead);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    if (bytesRead == 0) {
+      break; // EOF
+    }
+
+    totalBytesRead += bytesRead;
+    if (totalBytesRead >= UINT32_MAX) {
+      return NS_ERROR_SIGNED_JAR_ENTRY_TOO_LARGE;
+    }
+
+    rv = MapSECStatus(PK11_DigestOp(digestContext, buf.data, bytesRead));
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  if (totalBytesRead != len64) {
+    // The metadata we used for Available() doesn't match the actual size of
+    // the entry.
+    return NS_ERROR_SIGNED_JAR_ENTRY_INVALID;
+  }
+
+  // Verify that the digests match.
+  Digest digest;
+  rv = digest.End(SEC_OID_SHA1, digestContext);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (SECITEM_CompareItem(&digestFromManifest, &digest.get()) != SECEqual) {
+    return NS_ERROR_SIGNED_JAR_MODIFIED_ENTRY;
+  }
+
+  return NS_OK;
+}
+
+// On input, nextLineStart is the start of the current line. On output,
+// nextLineStart is the start of the next line.
+nsresult
+ReadLine(/*in/out*/ const char* & nextLineStart, /*out*/ nsCString & line,
+         bool allowContinuations = true)
+{
+  line.Truncate();
+  size_t previousLength = 0;
+  size_t currentLength = 0;
+  for (;;) {
+    const char* eol = PL_strpbrk(nextLineStart, "\r\n");
+
+    if (!eol) { // Reached end of file before newline
+      eol = nextLineStart + strlen(nextLineStart);
+    }
+
+    previousLength = currentLength;
+    line.Append(nextLineStart, eol - nextLineStart);
+    currentLength = line.Length();
+
+    // The spec says "No line may be longer than 72 bytes (not characters)"
+    // in its UTF8-encoded form.
+    static const size_t lineLimit = 72;
+    if (currentLength - previousLength > lineLimit) {
+      return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
+    }
+
+    // The spec says: "Implementations should support 65535-byte
+    // (not character) header values..."
+    if (currentLength > 65535) {
+      return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
+    }
+
+    if (*eol == '\r') {
+      ++eol;
+    }
+    if (*eol == '\n') {
+      ++eol;
+    }
+
+    nextLineStart = eol;
+
+    if (*eol != ' ') {
+      // not a continuation
+      return NS_OK;
+    }
+
+    // continuation
+    if (!allowContinuations) {
+      return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
+    }
+
+    ++nextLineStart; // skip space and keep appending
+  }
+}
+
+// The header strings are defined in the JAR specification.
+#define JAR_MF_SEARCH_STRING "(M|/M)ETA-INF/(M|m)(ANIFEST|anifest).(MF|mf)$"
+#define JAR_SF_SEARCH_STRING "(M|/M)ETA-INF/*.(SF|sf)$"
+#define JAR_RSA_SEARCH_STRING "(M|/M)ETA-INF/*.(RSA|rsa)$"
+#define JAR_MF_HEADER "Manifest-Version: 1.0"
+#define JAR_SF_HEADER "Signature-Version: 1.0"
+
+nsresult
+ParseAttribute(const nsAutoCString & curLine,
+               /*out*/ nsAutoCString & attrName,
+               /*out*/ nsAutoCString & attrValue)
+{
+  // Find the colon that separates the name from the value.
+  int32_t colonPos = curLine.FindChar(':');
+  if (colonPos == kNotFound) {
+    return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
+  }
+
+  // set attrName to the name, skipping spaces between the name and colon
+  int32_t nameEnd = colonPos;
+  for (;;) {
+    if (nameEnd == 0) {
+      return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; // colon with no name
+    }
+    if (curLine[nameEnd - 1] != ' ')
+      break;
+    --nameEnd;
+  }
+  curLine.Left(attrName, nameEnd);
+
+  // Set attrValue to the value, skipping spaces between the colon and the
+  // value. The value may be empty.
+  int32_t valueStart = colonPos + 1;
+  int32_t curLineLength = curLine.Length();
+  while (valueStart != curLineLength && curLine[valueStart] == ' ') {
+    ++valueStart;
+  }
+  curLine.Right(attrValue, curLineLength - valueStart);
+
+  return NS_OK;
+}
+
+// Parses the version line of the MF or SF header.
+nsresult
+CheckManifestVersion(const char* & nextLineStart,
+                     const nsACString & expectedHeader)
+{
+  // The JAR spec says: "Manifest-Version and Signature-Version must be first,
+  // and in exactly that case (so that they can be recognized easily as magic
+  // strings)."
+  nsAutoCString curLine;
+  nsresult rv = ReadLine(nextLineStart, curLine, false);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  if (!curLine.Equals(expectedHeader)) {
+    return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
+  }
+  return NS_OK;
+}
+
+// Parses a signature file (SF) as defined in the JDK 8 JAR Specification.
+//
+// The SF file *must* contain exactly one SHA1-Digest-Manifest attribute in
+// the main section. All other sections are ignored. This means that this will
+// NOT parse old-style signature files that have separate digests per entry.
+// The JDK8 x-Digest-Manifest variant is better because:
+//
+//   (1) It allows us to follow the principle that we should minimize the
+//       processing of data that we do before we verify its signature. In
+//       particular, with the x-Digest-Manifest style, we can verify the digest
+//       of MANIFEST.MF before we parse it, which prevents malicious JARs
+//       exploiting our MANIFEST.MF parser.
+//   (2) It is more time-efficient and space-efficient to have one
+//       x-Digest-Manifest instead of multiple x-Digest values.
+//
+// In order to get benefit (1), we do NOT implement the fallback to the older
+// mechanism as the spec requires/suggests. Also, for simplity's sake, we only
+// support exactly one SHA1-Digest-Manifest attribute, and no other
+// algorithms.
+//
+// filebuf must be null-terminated. On output, mfDigest will contain the
+// decoded value of SHA1-Digest-Manifest.
+nsresult
+ParseSF(const char* filebuf, /*out*/ SECItem & mfDigest)
+{
+  nsresult rv;
+
+  const char* nextLineStart = filebuf;
+  rv = CheckManifestVersion(nextLineStart, NS_LITERAL_CSTRING(JAR_SF_HEADER));
+  if (NS_FAILED(rv))
+    return rv;
+
+  // Find SHA1-Digest-Manifest
+  for (;;) {
+    nsAutoCString curLine;
+    rv = ReadLine(nextLineStart, curLine);
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+
+    if (curLine.Length() == 0) {
+      // End of main section (blank line or end-of-file), and no
+      // SHA1-Digest-Manifest found.
+      return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
+    }
+
+    nsAutoCString attrName;
+    nsAutoCString attrValue;
+    rv = ParseAttribute(curLine, attrName, attrValue);
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+
+    if (attrName.LowerCaseEqualsLiteral("sha1-digest-manifest")) {
+      rv = MapSECStatus(ATOB_ConvertAsciiToItem(&mfDigest, attrValue.get()));
+      if (NS_FAILED(rv)) {
+        return rv;
+      }
+
+      // There could be multiple SHA1-Digest-Manifest attributes, which
+      // would be an error, but it's better to just skip any erroneous
+      // duplicate entries rather than trying to detect them, because:
+      //
+      //   (1) It's simpler, and simpler generally means more secure
+      //   (2) An attacker can't make us accept a JAR we would otherwise
+      //       reject just by adding additional SHA1-Digest-Manifest
+      //       attributes.
+      break;
+    }
+
+    // ignore unrecognized attributes
+  }
+
+  return NS_OK;
+}
+
+// Parses MANIFEST.MF. The filenames of all entries will be returned in
+// mfItems. buf must be a pre-allocated scratch buffer that is used for doing
+// I/O.
+nsresult
+ParseMF(const char* filebuf, nsIZipReader * zip,
+        /*out*/ nsTHashtable<nsCStringHashKey> & mfItems,
+        ScopedAutoSECItem & buf)
+{
+  nsresult rv;
+
+  const char* nextLineStart = filebuf;
+
+  rv = CheckManifestVersion(nextLineStart, NS_LITERAL_CSTRING(JAR_MF_HEADER));
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  // Skip the rest of the header section, which ends with a blank line.
+  {
+    nsAutoCString line;
+    do {
+      rv = ReadLine(nextLineStart, line);
+      if (NS_FAILED(rv)) {
+        return rv;
+      }
+    } while (line.Length() > 0);
+
+    // Manifest containing no file entries is OK, though useless.
+    if (*nextLineStart == '\0') {
+      return NS_OK;
+    }
+  }
+
+  nsAutoCString curItemName;
+  ScopedAutoSECItem digest;
+
+  for (;;) {
+    nsAutoCString curLine;
+    rv = ReadLine(nextLineStart, curLine);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    if (curLine.Length() == 0) {
+      // end of section (blank line or end-of-file)
+
+      if (curItemName.Length() == 0) {
+        // '...Each section must start with an attribute with the name as
+        // "Name",...', so every section must have a Name attribute.
+        return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
+      }
+
+      if (digest.len == 0) {
+        // We require every entry to have a digest, since we require every
+        // entry to be signed and we don't allow duplicate entries.
+        return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
+      }
+
+      if (mfItems.Contains(curItemName)) {
+        // Duplicate entry
+        return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
+      }
+
+      // Verify that the entry's content digest matches the digest from this
+      // MF section.
+      rv = VerifyEntryContentDigest(zip, curItemName, digest, buf);
+      if (NS_FAILED(rv))
+        return rv;
+
+      mfItems.PutEntry(curItemName);
+
+      if (*nextLineStart == '\0') // end-of-file
+        break;
+
+      // reset so we know we haven't encountered either of these for the next
+      // item yet.
+      curItemName.Truncate();
+      digest.reset();
+
+      continue; // skip the rest of the loop below
+    }
+
+    nsAutoCString attrName;
+    nsAutoCString attrValue;
+    rv = ParseAttribute(curLine, attrName, attrValue);
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+
+    // Lines to look for:
+
+    // (1) Digest:
+    if (attrName.LowerCaseEqualsLiteral("sha1-digest"))
+    {
+      if (digest.len > 0) // multiple SHA1 digests in section
+        return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
+
+      rv = MapSECStatus(ATOB_ConvertAsciiToItem(&digest, attrValue.get()));
+      if (NS_FAILED(rv))
+        return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
+
+      continue;
+    }
+
+    // (2) Name: associates this manifest section with a file in the jar.
+    if (attrName.LowerCaseEqualsLiteral("name"))
+    {
+      if (MOZ_UNLIKELY(curItemName.Length() > 0)) // multiple names in section
+        return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
+
+      if (MOZ_UNLIKELY(attrValue.Length() == 0))
+        return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
+
+      curItemName = attrValue;
+
+      continue;
+    }
+
+    // (3) Magic: the only other must-understand attribute
+    if (attrName.LowerCaseEqualsLiteral("magic")) {
+      // We don't understand any magic, so we can't verify an entry that
+      // requires magic. Since we require every entry to have a valid
+      // signature, we have no choice but to reject the entry.
+      return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
+    }
+
+    // unrecognized attributes must be ignored
+  }
+
+  return NS_OK;
+}
+
+nsresult
+VerifySignature(AppTrustedRoot trustedRoot,
+                const SECItem& buffer, const SECItem& detachedDigest,
+        /*out*/ insanity::pkix::ScopedCERTCertList& builtChain)
+{
+  insanity::pkix::ScopedPtr<NSSCMSMessage, NSS_CMSMessage_Destroy>
+    cmsMsg(NSS_CMSMessage_CreateFromDER(const_cast<SECItem*>(&buffer), nullptr,
+                                        nullptr, nullptr, nullptr, nullptr,
+                                        nullptr));
+  if (!cmsMsg) {
+    return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING;
+  }
+
+  if (!NSS_CMSMessage_IsSigned(cmsMsg.get())) {
+    PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("CMS message isn't signed"));
+    return NS_ERROR_CMS_VERIFY_NOT_SIGNED;
+  }
+
+  NSSCMSContentInfo* cinfo = NSS_CMSMessage_ContentLevel(cmsMsg.get(), 0);
+  if (!cinfo) {
+    return NS_ERROR_CMS_VERIFY_NO_CONTENT_INFO;
+  }
+
+  // signedData is non-owning
+  NSSCMSSignedData* signedData =
+    reinterpret_cast<NSSCMSSignedData*>(NSS_CMSContentInfo_GetContent(cinfo));
+  if (!signedData) {
+    return NS_ERROR_CMS_VERIFY_NO_CONTENT_INFO;
+  }
+
+  // Set digest value.
+  if (NSS_CMSSignedData_SetDigestValue(signedData, SEC_OID_SHA1,
+                                       const_cast<SECItem*>(&detachedDigest))) {
+    return NS_ERROR_CMS_VERIFY_BAD_DIGEST;
+  }
+
+  // Parse the certificates into CERTCertificate objects held in memory, so that
+  // AppTrustDomain will be able to find them during path building.
+  insanity::pkix::ScopedCERTCertList certs(CERT_NewCertList());
+  if (!certs) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+  if (signedData->rawCerts) {
+    for (size_t i = 0; signedData->rawCerts[i]; ++i) {
+      insanity::pkix::ScopedCERTCertificate
+        cert(CERT_NewTempCertificate(CERT_GetDefaultCertDB(),
+                                     signedData->rawCerts[i], nullptr, false,
+                                     true));
+      // Skip certificates that fail to parse
+      if (cert) {
+        if (CERT_AddCertToListTail(certs.get(), cert.get()) == SECSuccess) {
+          cert.release(); // ownership transfered
+        } else {
+          return NS_ERROR_OUT_OF_MEMORY;
+        }
+      }
+    }
+  }
+
+  // Get the end-entity certificate.
+  int numSigners = NSS_CMSSignedData_SignerInfoCount(signedData);
+  if (NS_WARN_IF(numSigners != 1)) {
+    return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING;
+  }
+  // signer is non-owning.
+  NSSCMSSignerInfo* signer = NSS_CMSSignedData_GetSignerInfo(signedData, 0);
+  if (NS_WARN_IF(!signer)) {
+    return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING;
+  }
+  // cert is signerCert
+  CERTCertificate* signerCert =
+    NSS_CMSSignerInfo_GetSigningCertificate(signer, CERT_GetDefaultCertDB());
+  if (!signerCert) {
+    return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING;
+  }
+
+  // Verify certificate.
+  AppTrustDomain trustDomain(nullptr); // TODO: null pinArg
+  if (trustDomain.SetTrustedRoot(trustedRoot) != SECSuccess) {
+    return MapSECStatus(SECFailure);
+  }
+  if (BuildCertChain(trustDomain, signerCert, PR_Now(), MustBeEndEntity,
+                     KU_DIGITAL_SIGNATURE, SEC_OID_EXT_KEY_USAGE_CODE_SIGN,
+                     builtChain) != SECSuccess) {
+    return MapSECStatus(SECFailure);
+  }
+
+  // See NSS_CMSContentInfo_GetContentTypeOID, which isn't exported from NSS.
+  SECOidData* contentTypeOidData =
+    SECOID_FindOID(&signedData->contentInfo.contentType);
+  if (!contentTypeOidData) {
+    return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING;
+  }
+
+  return MapSECStatus(NSS_CMSSignerInfo_Verify(signer,
+                         const_cast<SECItem*>(&detachedDigest),
+                         &contentTypeOidData->oid));
+}
+
+NS_IMETHODIMP
+OpenSignedAppFile(AppTrustedRoot aTrustedRoot, nsIFile* aJarFile,
+                  /*out, optional */ nsIZipReader** aZipReader,
+                  /*out, optional */ nsIX509Cert3** aSignerCert)
+{
+  NS_ENSURE_ARG_POINTER(aJarFile);
+
+  if (aZipReader) {
+    *aZipReader = nullptr;
+  }
+
+  if (aSignerCert) {
+    *aSignerCert = nullptr;
+  }
+
+  nsresult rv;
+
+  static NS_DEFINE_CID(kZipReaderCID, NS_ZIPREADER_CID);
+  nsCOMPtr<nsIZipReader> zip = do_CreateInstance(kZipReaderCID, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = zip->Open(aJarFile);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Signature (RSA) file
+  nsAutoCString sigFilename;
+  ScopedAutoSECItem sigBuffer;
+  rv = FindAndLoadOneEntry(zip, nsLiteralCString(JAR_RSA_SEARCH_STRING),
+                           sigFilename, sigBuffer, nullptr);
+  if (NS_FAILED(rv)) {
+    return NS_ERROR_SIGNED_JAR_NOT_SIGNED;
+  }
+
+  // Signature (SF) file
+  nsAutoCString sfFilename;
+  ScopedAutoSECItem sfBuffer;
+  Digest sfCalculatedDigest;
+  rv = FindAndLoadOneEntry(zip, NS_LITERAL_CSTRING(JAR_SF_SEARCH_STRING),
+                           sfFilename, sfBuffer, &sfCalculatedDigest);
+  if (NS_FAILED(rv)) {
+    return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
+  }
+
+  sigBuffer.type = siBuffer;
+  insanity::pkix::ScopedCERTCertList builtChain;
+  rv = VerifySignature(aTrustedRoot, sigBuffer, sfCalculatedDigest.get(),
+                       builtChain);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  ScopedAutoSECItem mfDigest;
+  rv = ParseSF(char_ptr_cast(sfBuffer.data), mfDigest);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  // Manifest (MF) file
+  nsAutoCString mfFilename;
+  ScopedAutoSECItem manifestBuffer;
+  Digest mfCalculatedDigest;
+  rv = FindAndLoadOneEntry(zip, NS_LITERAL_CSTRING(JAR_MF_SEARCH_STRING),
+                           mfFilename, manifestBuffer, &mfCalculatedDigest);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  if (SECITEM_CompareItem(&mfDigest, &mfCalculatedDigest.get()) != SECEqual) {
+    return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
+  }
+
+  // Allocate the I/O buffer only once per JAR, instead of once per entry, in
+  // order to minimize malloc/free calls and in order to avoid fragmenting
+  // memory.
+  ScopedAutoSECItem buf(128 * 1024);
+
+  nsTHashtable<nsCStringHashKey> items;
+
+  rv = ParseMF(char_ptr_cast(manifestBuffer.data), zip, items, buf);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  // Verify every entry in the file.
+  nsCOMPtr<nsIUTF8StringEnumerator> entries;
+  rv = zip->FindEntries(EmptyCString(), getter_AddRefs(entries));
+  if (NS_SUCCEEDED(rv) && !entries) {
+    rv = NS_ERROR_UNEXPECTED;
+  }
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  for (;;) {
+    bool hasMore;
+    rv = entries->HasMore(&hasMore);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    if (!hasMore) {
+      break;
+    }
+
+    nsAutoCString entryFilename;
+    rv = entries->GetNext(entryFilename);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("Verifying digests for %s",
+           entryFilename.get()));
+
+    // The files that comprise the signature mechanism are not covered by the
+    // signature.
+    //
+    // XXX: This is OK for a single signature, but doesn't work for
+    // multiple signatures, because the metadata for the other signatures
+    // is not signed either.
+    if (entryFilename == mfFilename ||
+        entryFilename == sfFilename ||
+        entryFilename == sigFilename) {
+      continue;
+    }
+
+    if (entryFilename.Length() == 0) {
+      return NS_ERROR_SIGNED_JAR_ENTRY_INVALID;
+    }
+
+    // Entries with names that end in "/" are directory entries, which are not
+    // signed.
+    //
+    // XXX: As long as we don't unpack the JAR into the filesystem, the "/"
+    // entries are harmless. But, it is not clear what the security
+    // implications of directory entries are if/when we were to unpackage the
+    // JAR into the filesystem.
+    if (entryFilename[entryFilename.Length() - 1] == '/') {
+      continue;
+    }
+
+    nsCStringHashKey * item = items.GetEntry(entryFilename);
+    if (!item) {
+      return NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY;
+    }
+
+    // Remove the item so we can check for leftover items later
+    items.RemoveEntry(entryFilename);
+  }
+
+  // We verified that every entry that we require to be signed is signed. But,
+  // were there any missing entries--that is, entries that are mentioned in the
+  // manifest but missing from the archive?
+  if (items.Count() != 0) {
+    return NS_ERROR_SIGNED_JAR_ENTRY_MISSING;
+  }
+
+  // Return the reader to the caller if they want it
+  if (aZipReader) {
+    zip.forget(aZipReader);
+  }
+
+  // Return the signer's certificate to the reader if they want it.
+  // XXX: We should return an nsIX509CertList with the whole validated chain,
+  //      but we can't do that until we switch to libpkix.
+  if (aSignerCert) {
+    MOZ_ASSERT(CERT_LIST_HEAD(builtChain));
+    nsCOMPtr<nsIX509Cert3> signerCert =
+      nsNSSCertificate::Create(CERT_LIST_HEAD(builtChain)->cert);
+    NS_ENSURE_TRUE(signerCert, NS_ERROR_OUT_OF_MEMORY);
+    signerCert.forget(aSignerCert);
+  }
+
+  return NS_OK;
+}
+
+class OpenSignedAppFileTask MOZ_FINAL : public CryptoTask
+{
+public:
+  OpenSignedAppFileTask(AppTrustedRoot aTrustedRoot, nsIFile* aJarFile,
+                        nsIOpenSignedAppFileCallback* aCallback)
+    : mTrustedRoot(aTrustedRoot)
+    , mJarFile(aJarFile)
+    , mCallback(new nsMainThreadPtrHolder<nsIOpenSignedAppFileCallback>(aCallback))
+  {
+  }
+
+private:
+  virtual nsresult CalculateResult() MOZ_OVERRIDE
+  {
+    return OpenSignedAppFile(mTrustedRoot, mJarFile,
+                             getter_AddRefs(mZipReader),
+                             getter_AddRefs(mSignerCert));
+  }
+
+  // nsNSSCertificate implements nsNSSShutdownObject, so there's nothing that
+  // needs to be released
+  virtual void ReleaseNSSResources() { }
+
+  virtual void CallCallback(nsresult rv)
+  {
+    (void) mCallback->OpenSignedAppFileFinished(rv, mZipReader, mSignerCert);
+  }
+
+  const AppTrustedRoot mTrustedRoot;
+  const nsCOMPtr<nsIFile> mJarFile;
+  nsMainThreadPtrHandle<nsIOpenSignedAppFileCallback> mCallback;
+  nsCOMPtr<nsIZipReader> mZipReader; // out
+  nsCOMPtr<nsIX509Cert3> mSignerCert; // out
+};
+
+} // unnamed namespace
+
+NS_IMETHODIMP
+nsNSSCertificateDB::OpenSignedAppFileAsync(
+  AppTrustedRoot aTrustedRoot, nsIFile* aJarFile,
+  nsIOpenSignedAppFileCallback* aCallback)
+{
+  NS_ENSURE_ARG_POINTER(aJarFile);
+  NS_ENSURE_ARG_POINTER(aCallback);
+  RefPtr<OpenSignedAppFileTask> task(new OpenSignedAppFileTask(aTrustedRoot,
+                                                               aJarFile,
+                                                               aCallback));
+  return task->Dispatch("SignedJAR");
+}
new file mode 100644
--- /dev/null
+++ b/security/apps/AppTrustDomain.cpp
@@ -0,0 +1,181 @@
+/* -*- 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/. */
+
+#ifdef MOZ_LOGGING
+#define FORCE_PR_LOG 1
+#endif
+
+#include "AppTrustDomain.h"
+#include "certdb.h"
+#include "insanity/pkix.h"
+#include "mozilla/ArrayUtils.h"
+#include "nsIX509CertDB.h"
+#include "prerror.h"
+#include "secerr.h"
+
+// Generated in Makefile.in
+#include "marketplace-prod-public.inc"
+#include "marketplace-prod-reviewers.inc"
+#include "marketplace-dev-public.inc"
+#include "marketplace-dev-reviewers.inc"
+#include "xpcshell.inc"
+
+using namespace insanity::pkix;
+
+#ifdef PR_LOGGING
+extern PRLogModuleInfo* gPIPNSSLog;
+#endif
+
+namespace mozilla { namespace psm {
+
+AppTrustDomain::AppTrustDomain(void* pinArg)
+  : mPinArg(pinArg)
+{
+}
+
+SECStatus
+AppTrustDomain::SetTrustedRoot(AppTrustedRoot trustedRoot)
+{
+  SECItem trustedDER;
+
+  // Load the trusted certificate into the in-memory NSS database so that
+  // CERT_CreateSubjectCertList can find it.
+
+  switch (trustedRoot)
+  {
+    case nsIX509CertDB::AppMarketplaceProdPublicRoot:
+      trustedDER.data = const_cast<uint8_t*>(marketplaceProdPublicRoot);
+      trustedDER.len = mozilla::ArrayLength(marketplaceProdPublicRoot);
+      break;
+
+    case nsIX509CertDB::AppMarketplaceProdReviewersRoot:
+      trustedDER.data = const_cast<uint8_t*>(marketplaceProdReviewersRoot);
+      trustedDER.len = mozilla::ArrayLength(marketplaceProdReviewersRoot);
+      break;
+
+    case nsIX509CertDB::AppMarketplaceDevPublicRoot:
+      trustedDER.data = const_cast<uint8_t*>(marketplaceDevPublicRoot);
+      trustedDER.len = mozilla::ArrayLength(marketplaceDevPublicRoot);
+      break;
+
+    case nsIX509CertDB::AppMarketplaceDevReviewersRoot:
+      trustedDER.data = const_cast<uint8_t*>(marketplaceDevReviewersRoot);
+      trustedDER.len = mozilla::ArrayLength(marketplaceDevReviewersRoot);
+      break;
+
+    case nsIX509CertDB::AppXPCShellRoot:
+      trustedDER.data = const_cast<uint8_t*>(xpcshellRoot);
+      trustedDER.len = mozilla::ArrayLength(xpcshellRoot);
+      break;
+
+    default:
+      PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
+      return SECFailure;
+  }
+
+  mTrustedRoot = CERT_NewTempCertificate(CERT_GetDefaultCertDB(),
+                                         &trustedDER, nullptr, false, true);
+  if (!mTrustedRoot) {
+    return SECFailure;
+  }
+
+  return SECSuccess;
+}
+
+SECStatus
+AppTrustDomain::FindPotentialIssuers(const SECItem* encodedIssuerName,
+                                     PRTime time,
+                             /*out*/ insanity::pkix::ScopedCERTCertList& results)
+{
+  MOZ_ASSERT(mTrustedRoot);
+  if (!mTrustedRoot) {
+    PR_SetError(PR_INVALID_STATE_ERROR, 0);
+    return SECFailure;
+  }
+
+  results = CERT_CreateSubjectCertList(nullptr, CERT_GetDefaultCertDB(),
+                                       encodedIssuerName, time, true);
+  if (!results) {
+    // NSS sometimes returns this unhelpful error code upon failing to find any
+    // candidate certificates.
+    if (PR_GetError() == SEC_ERROR_BAD_DATABASE) {
+      PR_SetError(SEC_ERROR_UNKNOWN_ISSUER, 0);
+    }
+    return SECFailure;
+  }
+
+  return SECSuccess;
+}
+
+SECStatus
+AppTrustDomain::GetCertTrust(EndEntityOrCA endEntityOrCA,
+                             const CERTCertificate* candidateCert,
+                     /*out*/ TrustLevel* trustLevel)
+{
+  MOZ_ASSERT(candidateCert);
+  MOZ_ASSERT(trustLevel);
+  MOZ_ASSERT(mTrustedRoot);
+  if (!candidateCert || !trustLevel) {
+    PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
+    return SECFailure;
+  }
+  if (!mTrustedRoot) {
+    PR_SetError(PR_INVALID_STATE_ERROR, 0);
+    return SECFailure;
+  }
+
+  // Handle active distrust of the certificate.
+  CERTCertTrust trust;
+  if (CERT_GetCertTrust(candidateCert, &trust) == SECSuccess) {
+    PRUint32 flags = SEC_GET_TRUST_FLAGS(&trust, trustObjectSigning);
+
+    // For DISTRUST, we use the CERTDB_TRUSTED or CERTDB_TRUSTED_CA bit,
+    // because we can have active distrust for either type of cert. Note that
+    // CERTDB_TERMINAL_RECORD means "stop trying to inherit trust" so if the
+    // relevant trust bit isn't set then that means the cert must be considered
+    // distrusted.
+    PRUint32 relevantTrustBit = endEntityOrCA == MustBeCA
+                              ? CERTDB_TRUSTED_CA
+                              : CERTDB_TRUSTED;
+    if (((flags & (relevantTrustBit | CERTDB_TERMINAL_RECORD)))
+            == CERTDB_TERMINAL_RECORD) {
+      *trustLevel = ActivelyDistrusted;
+      return SECSuccess;
+    }
+
+#ifdef MOZ_B2G_CERTDATA
+    // XXX(Bug 972201): We have to allow the old way of supporting additional
+    // roots until we fix bug 889744. Remove this along with the rest of the
+    // MOZ_B2G_CERTDATA stuff.
+
+    // For TRUST, we only use the CERTDB_TRUSTED_CA bit, because Gecko hasn't
+    // needed to consider end-entity certs to be their own trust anchors since
+    // Gecko implemented nsICertOverrideService.
+    if (flags & CERTDB_TRUSTED_CA) {
+      *trustLevel = TrustAnchor;
+      return SECSuccess;
+    }
+#endif
+  }
+
+  // mTrustedRoot is the only trust anchor for this validation.
+  if (CERT_CompareCerts(mTrustedRoot.get(), candidateCert)) {
+    *trustLevel = TrustAnchor;
+    return SECSuccess;
+  }
+
+  *trustLevel = InheritsTrust;
+  return SECSuccess;
+}
+
+SECStatus
+AppTrustDomain::VerifySignedData(const CERTSignedData* signedData,
+                                  const CERTCertificate* cert)
+{
+  return ::insanity::pkix::VerifySignedData(signedData, cert, mPinArg);
+}
+
+} }
new file mode 100644
--- /dev/null
+++ b/security/apps/AppTrustDomain.h
@@ -0,0 +1,40 @@
+/* -*- 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/. */
+
+#ifndef mozilla_psm_AppsTrustDomain_h
+#define mozilla_psm_AppsTrustDomain_h
+
+#include "insanity/pkixtypes.h"
+#include "nsDebug.h"
+#include "nsIX509CertDB.h"
+
+namespace mozilla { namespace psm {
+
+class AppTrustDomain MOZ_FINAL : public insanity::pkix::TrustDomain
+{
+public:
+  AppTrustDomain(void* pinArg);
+
+  SECStatus SetTrustedRoot(AppTrustedRoot trustedRoot);
+
+  SECStatus GetCertTrust(insanity::pkix::EndEntityOrCA endEntityOrCA,
+                         const CERTCertificate* candidateCert,
+                 /*out*/ TrustLevel* trustLevel) MOZ_OVERRIDE;
+  SECStatus FindPotentialIssuers(const SECItem* encodedIssuerName,
+                                 PRTime time,
+                         /*out*/ insanity::pkix::ScopedCERTCertList& results)
+                                 MOZ_OVERRIDE;
+  SECStatus VerifySignedData(const CERTSignedData* signedData,
+                             const CERTCertificate* cert) MOZ_OVERRIDE;
+
+private:
+  void* mPinArg; // non-owning!
+  insanity::pkix::ScopedCERTCertificate mTrustedRoot;
+};
+
+} } // namespace mozilla::psm
+
+#endif // mozilla_psm_AppsTrustDomain_h
new file mode 100644
--- /dev/null
+++ b/security/apps/Makefile.in
@@ -0,0 +1,29 @@
+#
+# 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/.
+
+GEN_CERT_HEADER = $(srcdir)/gen_cert_header.py
+
+marketplace-prod-public.inc: marketplace-prod-public.crt $(GEN_CERT_HEADER)
+	$(PYTHON) $(GEN_CERT_HEADER) marketplaceProdPublicRoot $< > $@
+
+marketplace-prod-reviewers.inc: marketplace-prod-reviewers.crt $(GEN_CERT_HEADER)
+	$(PYTHON) $(GEN_CERT_HEADER) marketplaceProdReviewersRoot $< > $@
+
+marketplace-dev-public.inc: marketplace-dev-public.crt $(GEN_CERT_HEADER)
+	$(PYTHON) $(GEN_CERT_HEADER) marketplaceDevPublicRoot $< > $@
+
+marketplace-dev-reviewers.inc: marketplace-dev-reviewers.crt $(GEN_CERT_HEADER)
+	$(PYTHON) $(GEN_CERT_HEADER) marketplaceDevReviewersRoot $< > $@
+
+xpcshell.inc: $(srcdir)/../manager/ssl/tests/unit/test_signed_apps/trusted_ca1.der $(GEN_CERT_HEADER)
+	$(PYTHON) $(GEN_CERT_HEADER) xpcshellRoot $< > $@
+
+export:: \
+  marketplace-prod-public.inc \
+  marketplace-prod-reviewers.inc \
+  marketplace-dev-public.inc \
+  marketplace-dev-reviewers.inc \
+  xpcshell.inc \
+  $(NULL)
new file mode 100644
--- /dev/null
+++ b/security/apps/gen_cert_header.py
@@ -0,0 +1,29 @@
+# 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/.
+
+import sys
+import binascii
+
+def file_byte_generator(filename, block_size = 512):
+  with open(filename, "rb") as f:
+    while True:
+      block = f.read(block_size)
+      if block:
+        for byte in block:
+          yield byte
+      else:
+        break
+
+def create_header(array_name, in_filename):
+  hexified = ["0x" + binascii.hexlify(byte) for byte in file_byte_generator(in_filename)]
+  print "const uint8_t " + array_name + "[] = {"
+  print ", ".join(hexified)
+  print "};"
+  return 0
+
+if __name__ == '__main__':
+  if len(sys.argv) < 3:
+    print 'ERROR: usage: gen_cert_header.py array_name in_filename'
+    sys.exit(1);
+  sys.exit(create_header(sys.argv[1], sys.argv[2]))
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..490b8682b75cb9b6cee68708dd25a331a055d16d
GIT binary patch
literal 964
zc$_n6Vm@Hd#I#}oGZP~d6C<MmFB_*;n@8JsUPeY%RtAH{7DH(RNjBzC7G_}~*NVj4
zf}EU0g`oWW5(Q^R1w%s-10j$kw=g$c(%I2a)<7C0$|Wp<5X~<t$S+DP$;{6)6f+P3
zNwW*{`sSDBl_X~7DTHOFmK$;#aDv1+gqeV5Ksju}OrgOBa^k#(MutFSWNctyU>qgR
zYXsyPT0*&l#hFcvO2`4o$jZRn#K_M86z5`UVq|2vwYFjB>-~>=1;jUMzc;k~Tk>;?
z+;slczh@h^yX~%$|2KWEN8n-8oCkO1|8iVhXT81d?xoKwHSN#FCSSa}ZAHMPL)Sk_
zuRF3m?COoB+d~D;d|UG2rc})$SKA5o(dTcy-n0Hv@WUWU(?zuwO2O8xFZFL6tzogg
zk{v9#)|1QU#Gm(n8QEq2%=Pptl)F56%BFh{=F56E9S>Toz1~#*$6Oxk_VXV9TD|Vx
z4|_a^d8+g~tDhTdA7^h`FFC_+XF%g_(Lb)w!mpjVbJy*jPmb9Kt5Zvv=l?&Hz4Lzg
zetqZEtF3eY@CEFzl>Gh4nX%S4k^e-{<M7B?OYHSFzf{?6>oRT1@{8NfGchwVFfO(>
zur%NY2AZreBjbM-Rs&`rWxxj#;0Fn?Ff%c+7>I%Rsvtg(0T&yGHXATGvNJQIrB7y1
zs7?>?KJkZ%skq#xXtqdx??y)3#!@|R=j)S_>{2HwPB#;-y_~}LYk%=4r@D2_?zT79
z9y>C5+s?GC1pYET89}XR4uAj1o|vyvxp!>><d{AR%>CA!y6N@y{l@=0-hQmIpRkJg
zZ`B6Zljnu+&(yx&rDeVFXR6OytwWL-j3pbYL)zxNovJq_ufdEV`Ksr`{Msu=!z{J0
z7XQ+}+n%6!Oya}r&fhmzeK0?^NBw=txh45Q%O$QIcqJ*nvF`SxdlTDvdXo68j{Z<f
zh_;FE2naja^y2pON2{;i*jHd<wr!)Jo=Mh^SJO^9{5w2#ciQp2W+ytQJxL2woG;(a
Pc-6~WXN8B7zU%}5z$aza
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..023f6c1edc6a6a57520dcde6660e0291a1ef4ae4
GIT binary patch
literal 1012
zc$_n6V*X&z#B^f;GZP~d6C<MmFB_*;n@8JsUPeY%RtAH{IflvxifqiGEX=}EzKKQI
zsU-zDiOH!Usl_Em3PJh#B?``t3WkO<22vnJ+`_^bikuw{RSlFuvRuM4n6mjr1^GpZ
zC7JnohGGUHAZ6^ryuSIRc_oRNc?w~fspW>;2Am*q4q+ysSx^p}FjHu-ft)z6p%D-o
z8yOlHm>5Ng^BRG;hEVQccYYJ25^{huvNA9?G4eA2#krW87#SJX2P?C!J9hbhzNo=k
z1&fZ8J9pjM{JYN1ps=-O_S{cjMeIvvs+1gznI%@~&M>Pu`Nl@qO4hBv3Yu;#UUF1v
zngbWJn|8#0*N2O$!+nH}T&M4=KEY{JJTZB<wD`Z<)u*m6bK>Ht3J%D&FpLNjYdE}o
z>-)2lB3+I%+uJ?M4!^UL?aXwY6W0B<(LxXXBoD53`TSSg<nWCh`!@BI#!k2FUH>I>
z#$wr562A_Y3*B0}qx@A@i_qI+a`sIxCrr=T9e8)*+RMBGQew+57H#(X^=pn+g53Q|
ze%BWoY4We!+Tu3v^{`n}p8C2)`Y>zDJ&T#g7By{|Cg)IM7Vg^JT=Q5brsKO<Hxn}>
z1LI<A14{#bV4%qgGcx{XVKra|QU-h=0e+AG3o{cFi-8!3uL|Px7;v$1XtM#6FFP|c
zT6$&%g=(+E^KA*&UQ9@je;YPyQoNDCS~*Kr_1aFG8LK`8>dyOcQ{rTrSAxCFk%Ul9
zUAdo!ZLIUYypI+XQcLhF@P8L8`=_Dy^Lc5P<9}UTd+j=;Gbh;R{m{E<S|PmpkNX7k
z&1!G{acaH4;I(3YP9w{&xhx6>i5sJt6YkutYC1OY0dokqm+seB@2aJ2UPo6I|JwI8
zOz_T}`l}_Ef3M8Ti=Ff3uIyF5c?TYd{d9C+YkJt(X~nTE=ASP;tK<xm{@txHMWbE&
zAx|b_HIMeY{5NGkWHnFEy{dIvKlM<k<5!m&BkB4B$7(F+1n#ooh*s4Oi5HVGEBKpy
TI^@QbQ*IpDhn~86Wkvx2Yqw~0
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..85c2fed92a963b2ff51ee92165d6ed59fa70aeff
GIT binary patch
literal 1177
zc$_n6Vwq~t#9X_8nTe5!iILHOmyJ`a&7<u*FC!xlD}zB}uOYVqCmVAp3!5-gXt1G-
zffR_tB`oZlUzM4Ylc?aFUsRA^lvt9PpJ!-pU<#7u7S@Bw`X&}-r<N4tBqpaS1Qg|`
zlqQ1|Dg<Yy=Vj)lD+H$&m1QQU8mb$pf;2J<%N6D4m*^%Z>K4Ei>*gZNFpv}HH8eIb
zGBP(XFf=hWixTHG0&z{D+(GBwCPoG1AZ27_U~XdMWdMqEFf}nUGAu93?6x~G>GPeO
zv@|}0)6I{fIUCzrAI{tAS#&?A*vCgt_>ah+{1%VNA#=Twve+~K9h!HxJR`$=b$fMy
zEPEAS>}EsXmGX7}W43Nr4qkg(rK545z=IO?FJWhy@-BTd*=qdN``Uz{ZqC)(f*ErY
z*jF)xFTdbfwcpU;YTeo|-$FuJ?cTPm<6N`gw}IaaKL;M!`;Oed!c-Ptn|^66lR%GE
z$iB`Tmt}?9tMZoL{;H<eEwNRyd;Y7njCT%a#z*Y6P+@v<Ms=U<;kA*m+RGXY+H4{+
zbRBx9e^^v7Wuf!?I{{2HEsXxkU(8tcVr$Pv3Cm!Q{kaz&o&Dfu`Qo2Qb<x8Ii~ch)
zGB0lYZ_xP1fCm_$vcfE^2F#3%{|)#+JboaHg$0;0*$l)$d{q#i$AF8CLz|6}m6e^D
z*`RR=NJ5#VagIUb%!Q4Umo!coY-s=(%?7d{SMsrlv4|wse|m7qNO8vhZOW-$_8<1X
zI=(aqEz>cB5{B{V2Ma2CR;O0jE$YAjNxqut$xi!SdZJrdbq!y-II=C9GLL(8o!Y$F
z9^0nQu#waL=4#bny6{5l#I6Z?Jdd{8rdR!Zzn102?mVxu2geSX$D02+86NPEoBcrJ
z(wb><tCdvx`#;E7e9C(4T{h+CkwBSA@7DTqwiF1Y?D}~7VP@=3!{RF(`yQ@}6)yT$
zyY@xt>jlOu%%h%mR(yZ5P$jSNJBL*mhoN@p(!SD7>ie&n9QmVrd(W*eA3K7&T^2t&
z_e6ftVs)OF$=}|6RyWOE7_rLgLdU5$$5c0@g<Nm<FF6pod}ry8p4IQ_Ej17Cj{oX#
ZI(c4CbiY}~YI(0&s*9Zxw%F<11^|(Pxb*-4
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..53be8c81ed601f2b054b55df1ab94eff1ee767ce
GIT binary patch
literal 1171
zc$_n6V(B+%VlH36%*4pV#K>sC%f_kI=F#?@mywZ&mBFB~+mPFUlZ`o)g-w_#G}uta
zKnld+5*GH&ugc8HNmOvoFDl3{N-W9D&oeYLFagPO3+uvUeG`kaQ%eeR5|dLEf>O&e
zQ_E9}iWP!0)AKU((iMVJi^?*SQw`M&R6zQeg=LHK^GkG-6LpJVYISoFCK$+x^BNi(
z7#W!x7#JBCm`9288iBZ`Q0^deZ4;vca&R)TGB7tW@-hI$IhdLl85u6ztP?ok{@kh~
z`f>5TMW?P@?92O}nZJ8!$_1&OpR$S{Py8%2DtqOYJK=e?^qPtMj2~J)exKkLD!5H$
z#e=oHHPyOn6CN*0m=e0#(Ra%^zo&Qq<gYpIztiGR&870Qsh7AH1{-dPk2}CV^NRz6
z=989y6>~Zyo=lq)$u{$y*CHhjK}Y`kzZ4UvvB)lV7gzo2#@_kvcvR`KuXC7xG%*Vw
zEUKKc>hiy%F6|q(rsRL_dunL8XSe;rgNjxw?-<Y9|2O2HQ<zP{{>If)(!YpVasNGL
z6QB3tvG&nV2bb1r>zdv2tI%_2nVPxox!dcD6aL6N_@DmHD9O#yp+DfQQbIH5=0nUm
zznK`B7dQSjX#8cs0}M}DVHQ>cW=6*U27DkMKaj=30!)=`24Wz-Du~Zxz{SR)&Bn;e
z%FfJe(6|UBq0G`a%b;=k!p4b98v6!U3IIm3fh@?4d@N!tB3D*#O+UW*XJ=Bm&;1)=
zwb#;%B9EeFH)c?>m^kbF;}7$X```OH%W=(#NlzAW>%?~S{_ov;_A_&PJ^!O^dd=#4
z`B^rf{kJ#9%5=8lxzB&a-v7LiI9t9m@$beNewD8T{vDZg<^J~2UX9`_SDO}j8tiD_
zdnw_4^2d~;!CXF7DbY7{<BRV65D8e_ac<@NC<EQl2Uqoi&gN-bHLd>deQqs}&d!MW
zm4`&5USH-~a^mo|387wF??&=%Eo}d{MR$SpM};m1|GLiAtRbZVnK9vKTxPsqp0Vwg
z=C9~>w@v@pDnI3(IDdQj&yCNcwzqiJq)&->{PXBs@9O1+7t{Y#%y_WqnT5@S?V1en
c8QkA@HeNSWH)=m~Siyz!$jpqi?0a#l014^C)c^nh
new file mode 100644
--- /dev/null
+++ b/security/apps/moz.build
@@ -0,0 +1,25 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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/.
+
+# These cannot be built in unified mode because they force NSPR logging.
+SOURCES += [
+    'AppSignatureVerification.cpp',
+    'AppTrustDomain.cpp',
+]
+
+FAIL_ON_WARNINGS = True
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+    '../certverifier',
+    '../insanity/include',
+    '../manager/ssl/src',
+]
+
+DEFINES['NSS_ENABLE_ECC'] = 'True'
+for var in ('DLL_PREFIX', 'DLL_SUFFIX'):
+    DEFINES[var] = '"%s"' % CONFIG[var]
deleted file mode 100644
index 85c2fed92a963b2ff51ee92165d6ed59fa70aeff..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
--- a/security/certverifier/NSSCertDBTrustDomain.cpp
+++ b/security/certverifier/NSSCertDBTrustDomain.cpp
@@ -54,16 +54,21 @@ NSSCertDBTrustDomain::FindPotentialIssue
   /*out*/ insanity::pkix::ScopedCERTCertList& results)
 {
   // TODO: normalize encodedIssuerName
   // TODO: NSS seems to be ambiguous between "no potential issuers found" and
   // "there was an error trying to retrieve the potential issuers."
   results = CERT_CreateSubjectCertList(nullptr, CERT_GetDefaultCertDB(),
                                        encodedIssuerName, time, true);
   if (!results) {
+    // NSS sometimes returns this unhelpful error code upon failing to find any
+    // candidate certificates.
+    if (PR_GetError() == SEC_ERROR_BAD_DATABASE) {
+      PR_SetError(SEC_ERROR_UNKNOWN_ISSUER, 0);
+    }
     return SECFailure;
   }
 
   return SECSuccess;
 }
 
 SECStatus
 NSSCertDBTrustDomain::GetCertTrust(EndEntityOrCA endEntityOrCA,
--- a/security/manager/ssl/public/nsIX509CertDB.idl
+++ b/security/manager/ssl/public/nsIX509CertDB.idl
@@ -14,29 +14,31 @@ interface nsIInterfaceRequestor;
 interface nsIZipReader;
 interface nsIRecentBadCerts;
 interface nsIX509CertList;
 
 %{C++
 #define NS_X509CERTDB_CONTRACTID "@mozilla.org/security/x509certdb;1"
 %}
 
-[scriptable, function, uuid(25a048e8-bb1c-4c33-ad3a-eacf2ad9e9ee)]
-interface nsIOpenSignedJARFileCallback : nsISupports
+typedef uint32_t AppTrustedRoot;
+
+[scriptable, function, uuid(e12aec59-7153-4e84-9376-2e851311b7a3)]
+interface nsIOpenSignedAppFileCallback : nsISupports
 {
-  void openSignedJARFileFinished(in nsresult rv,
+  void openSignedAppFileFinished(in nsresult rv,
                                  in nsIZipReader aZipReader,
                                  in nsIX509Cert3 aSignerCert);
 };
 
 /**
  * This represents a service to access and manipulate 
  * X.509 certificates stored in a database.
  */
-[scriptable, uuid(38463592-8527-11e3-b240-180373d97f23)]
+[scriptable, uuid(398dfa21-f29d-4530-bb42-136c6e7d9486)]
 interface nsIX509CertDB : nsISupports {
 
   /**
    *  Constants that define which usages a certificate
    *  is trusted for.
    */
   const unsigned long UNTRUSTED       =      0;
   const unsigned long TRUSTED_SSL     = 1 << 0;
@@ -292,18 +294,24 @@ interface nsIX509CertDB : nsISupports {
    *  signed the JAR are returned.
    *
    *  On failure, an error code is returned.
    *
    *  This method returns a nsIZipReader, instead of taking an nsIZipReader
    *  as input, to encourage users of the API to verify the signature as the
    *  first step in opening the JAR.
    */
-  void openSignedJARFileAsync(in nsIFile aJarFile,
-                              in nsIOpenSignedJARFileCallback callback);
+  const AppTrustedRoot AppMarketplaceProdPublicRoot = 1;
+  const AppTrustedRoot AppMarketplaceProdReviewersRoot = 2;
+  const AppTrustedRoot AppMarketplaceDevPublicRoot = 3;
+  const AppTrustedRoot AppMarketplaceDevReviewersRoot = 4;
+  const AppTrustedRoot AppXPCShellRoot = 5;
+  void openSignedAppFileAsync(in AppTrustedRoot trustedRoot,
+                              in nsIFile aJarFile,
+                              in nsIOpenSignedAppFileCallback callback);
 
   /* 
    * Add a cert to a cert DB from a binary string.
    *
    * @param certDER The raw DER encoding of a certificate.
    * @param aTrust decoded by CERT_DecodeTrustString. 3 comma separated characters,
    *                indicating SSL, Email, and Obj signing trust
    * @param aName name of the cert for display purposes.
deleted file mode 100644
--- a/security/manager/ssl/src/JARSignatureVerification.cpp
+++ /dev/null
@@ -1,776 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=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/. */
-
-#ifdef MOZ_LOGGING
-#define FORCE_PR_LOG 1
-#endif
-
-#include "nsNSSCertificateDB.h"
-
-#include "mozilla/RefPtr.h"
-#include "CryptoTask.h"
-#include "nsComponentManagerUtils.h"
-#include "nsCOMPtr.h"
-#include "nsHashKeys.h"
-#include "nsIFile.h"
-#include "nsIInputStream.h"
-#include "nsIStringEnumerator.h"
-#include "nsIZipReader.h"
-#include "nsNSSCertificate.h"
-#include "nsProxyRelease.h"
-#include "nsString.h"
-#include "nsTHashtable.h"
-#include "ScopedNSSTypes.h"
-
-#include "base64.h"
-#include "secmime.h"
-#include "plstr.h"
-#include "prlog.h"
-
-using namespace mozilla;
-
-#ifdef PR_LOGGING
-extern PRLogModuleInfo* gPIPNSSLog;
-#endif
-
-namespace {
-
-// Finds exactly one (signature metadata) entry that matches the given
-// search pattern, and then load it. Fails if there are no matches or if
-// there is more than one match. If bugDigest is not null then on success
-// bufDigest will contain the SHA-1 digeset of the entry.
-nsresult
-FindAndLoadOneEntry(nsIZipReader * zip,
-                    const nsACString & searchPattern,
-                    /*out*/ nsACString & filename,
-                    /*out*/ SECItem & buf,
-                    /*optional, out*/ Digest * bufDigest)
-{
-  nsCOMPtr<nsIUTF8StringEnumerator> files;
-  nsresult rv = zip->FindEntries(searchPattern, getter_AddRefs(files));
-  if (NS_FAILED(rv) || !files) {
-    return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
-  }
-
-  bool more;
-  rv = files->HasMore(&more);
-  NS_ENSURE_SUCCESS(rv, rv);
-  if (!more) {
-    return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
-  }
-
-  rv = files->GetNext(filename);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  // Check if there is more than one match, if so then error!
-  rv = files->HasMore(&more);
-  NS_ENSURE_SUCCESS(rv, rv);
-  if (more) {
-    return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
-  }
-
-  nsCOMPtr<nsIInputStream> stream;
-  rv = zip->GetInputStream(filename, getter_AddRefs(stream));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  // The size returned by Available() might be inaccurate so we need to check
-  // that Available() matches up with the actual length of the file.
-  uint64_t len64;
-  rv = stream->Available(&len64);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-
-  // Cap the maximum accepted size of signature-related files at 1MB (which is
-  // still crazily huge) to avoid OOM. The uncompressed length of an entry can be
-  // hundreds of times larger than the compressed version, especially if
-  // someone has speifically crafted the entry to cause OOM or to consume
-  // massive amounts of disk space.
-  //
-  // Also, keep in mind bug 164695 and that we must leave room for
-  // null-terminating the buffer.
-  static const uint32_t MAX_LENGTH = 1024 * 1024;
-  static_assert(MAX_LENGTH < UINT32_MAX, "MAX_LENGTH < UINT32_MAX");
-  NS_ENSURE_TRUE(len64 < MAX_LENGTH, NS_ERROR_FILE_CORRUPTED);
-  NS_ENSURE_TRUE(len64 < UINT32_MAX, NS_ERROR_FILE_CORRUPTED); // bug 164695
-  SECITEM_AllocItem(buf, static_cast<uint32_t>(len64 + 1));
-
-  // buf.len == len64 + 1. We attempt to read len64 + 1 bytes instead of len64,
-  // so that we can check whether the metadata in the ZIP for the entry is
-  // incorrect.
-  uint32_t bytesRead;
-  rv = stream->Read(char_ptr_cast(buf.data), buf.len, &bytesRead);
-  NS_ENSURE_SUCCESS(rv, rv);
-  if (bytesRead != len64) {
-    return NS_ERROR_SIGNED_JAR_ENTRY_INVALID;
-  }
-
-  buf.data[buf.len - 1] = 0; // null-terminate
-
-  if (bufDigest) {
-    rv = bufDigest->DigestBuf(SEC_OID_SHA1, buf.data, buf.len - 1);
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-
-  return NS_OK;
-}
-
-// Verify the digest of an entry. We avoid loading the entire entry into memory
-// at once, which would require memory in proportion to the size of the largest
-// entry. Instead, we require only a small, fixed amount of memory.
-//
-// @param digestFromManifest The digest that we're supposed to check the file's
-//                           contents against, from the manifest
-// @param buf A scratch buffer that we use for doing the I/O, which must have
-//            already been allocated. The size of this buffer is the unit
-//            size of our I/O.
-nsresult
-VerifyEntryContentDigest(nsIZipReader * zip, const nsACString & aFilename,
-                         const SECItem & digestFromManifest, SECItem & buf)
-{
-  MOZ_ASSERT(buf.len > 0);
-  if (digestFromManifest.len != SHA1_LENGTH)
-    return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
-
-  nsresult rv;
-
-  nsCOMPtr<nsIInputStream> stream;
-  rv = zip->GetInputStream(aFilename, getter_AddRefs(stream));
-  if (NS_FAILED(rv)) {
-    return NS_ERROR_SIGNED_JAR_ENTRY_MISSING;
-  }
-
-  uint64_t len64;
-  rv = stream->Available(&len64);
-  NS_ENSURE_SUCCESS(rv, rv);
-  if (len64 > UINT32_MAX) {
-    return NS_ERROR_SIGNED_JAR_ENTRY_TOO_LARGE;
-  }
-
-  ScopedPK11Context digestContext(PK11_CreateDigestContext(SEC_OID_SHA1));
-  if (!digestContext) {
-    return PRErrorCode_to_nsresult(PR_GetError());
-  }
-
-  rv = MapSECStatus(PK11_DigestBegin(digestContext));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  uint64_t totalBytesRead = 0;
-  for (;;) {
-    uint32_t bytesRead;
-    rv = stream->Read(char_ptr_cast(buf.data), buf.len, &bytesRead);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    if (bytesRead == 0) {
-      break; // EOF
-    }
-
-    totalBytesRead += bytesRead;
-    if (totalBytesRead >= UINT32_MAX) {
-      return NS_ERROR_SIGNED_JAR_ENTRY_TOO_LARGE;
-    }
-
-    rv = MapSECStatus(PK11_DigestOp(digestContext, buf.data, bytesRead));
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-
-  if (totalBytesRead != len64) {
-    // The metadata we used for Available() doesn't match the actual size of
-    // the entry.
-    return NS_ERROR_SIGNED_JAR_ENTRY_INVALID;
-  }
-
-  // Verify that the digests match.
-  Digest digest;
-  rv = digest.End(SEC_OID_SHA1, digestContext);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  if (SECITEM_CompareItem(&digestFromManifest, &digest.get()) != SECEqual) {
-    return NS_ERROR_SIGNED_JAR_MODIFIED_ENTRY;
-  }
-
-  return NS_OK;
-}
-
-// On input, nextLineStart is the start of the current line. On output,
-// nextLineStart is the start of the next line.
-nsresult
-ReadLine(/*in/out*/ const char* & nextLineStart, /*out*/ nsCString & line,
-         bool allowContinuations = true)
-{
-  line.Truncate();
-  size_t previousLength = 0;
-  size_t currentLength = 0;
-  for (;;) {
-    const char* eol = PL_strpbrk(nextLineStart, "\r\n");
-
-    if (!eol) { // Reached end of file before newline
-      eol = nextLineStart + strlen(nextLineStart);
-    }
-
-    previousLength = currentLength;
-    line.Append(nextLineStart, eol - nextLineStart);
-    currentLength = line.Length();
-
-    // The spec says "No line may be longer than 72 bytes (not characters)"
-    // in its UTF8-encoded form.
-    static const size_t lineLimit = 72;
-    if (currentLength - previousLength > lineLimit) {
-      return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
-    }
-
-    // The spec says: "Implementations should support 65535-byte
-    // (not character) header values..."
-    if (currentLength > 65535) {
-      return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
-    }
-
-    if (*eol == '\r') {
-      ++eol;
-    }
-    if (*eol == '\n') {
-      ++eol;
-    }
-
-    nextLineStart = eol;
-
-    if (*eol != ' ') {
-      // not a continuation
-      return NS_OK;
-    }
-
-    // continuation
-    if (!allowContinuations) {
-      return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
-    }
-
-    ++nextLineStart; // skip space and keep appending
-  }
-}
-
-// The header strings are defined in the JAR specification.
-#define JAR_MF_SEARCH_STRING "(M|/M)ETA-INF/(M|m)(ANIFEST|anifest).(MF|mf)$"
-#define JAR_SF_SEARCH_STRING "(M|/M)ETA-INF/*.(SF|sf)$"
-#define JAR_RSA_SEARCH_STRING "(M|/M)ETA-INF/*.(RSA|rsa)$"
-#define JAR_MF_HEADER "Manifest-Version: 1.0"
-#define JAR_SF_HEADER "Signature-Version: 1.0"
-
-nsresult
-ParseAttribute(const nsAutoCString & curLine,
-               /*out*/ nsAutoCString & attrName,
-               /*out*/ nsAutoCString & attrValue)
-{
-  // Find the colon that separates the name from the value.
-  int32_t colonPos = curLine.FindChar(':');
-  if (colonPos == kNotFound) {
-    return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
-  }
-
-  // set attrName to the name, skipping spaces between the name and colon
-  int32_t nameEnd = colonPos;
-  for (;;) {
-    if (nameEnd == 0) {
-      return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; // colon with no name
-    }
-    if (curLine[nameEnd - 1] != ' ')
-      break;
-    --nameEnd;
-  }
-  curLine.Left(attrName, nameEnd);
-
-  // Set attrValue to the value, skipping spaces between the colon and the
-  // value. The value may be empty.
-  int32_t valueStart = colonPos + 1;
-  int32_t curLineLength = curLine.Length();
-  while (valueStart != curLineLength && curLine[valueStart] == ' ') {
-    ++valueStart;
-  }
-  curLine.Right(attrValue, curLineLength - valueStart);
-
-  return NS_OK;
-}
-
-// Parses the version line of the MF or SF header.
-nsresult
-CheckManifestVersion(const char* & nextLineStart,
-                     const nsACString & expectedHeader)
-{
-  // The JAR spec says: "Manifest-Version and Signature-Version must be first,
-  // and in exactly that case (so that they can be recognized easily as magic
-  // strings)."
-  nsAutoCString curLine;
-  nsresult rv = ReadLine(nextLineStart, curLine, false);
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-  if (!curLine.Equals(expectedHeader)) {
-    return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
-  }
-  return NS_OK;
-}
-
-// Parses a signature file (SF) as defined in the JDK 8 JAR Specification.
-//
-// The SF file *must* contain exactly one SHA1-Digest-Manifest attribute in
-// the main section. All other sections are ignored. This means that this will
-// NOT parse old-style signature files that have separate digests per entry.
-// The JDK8 x-Digest-Manifest variant is better because:
-//
-//   (1) It allows us to follow the principle that we should minimize the
-//       processing of data that we do before we verify its signature. In
-//       particular, with the x-Digest-Manifest style, we can verify the digest
-//       of MANIFEST.MF before we parse it, which prevents malicious JARs
-//       exploiting our MANIFEST.MF parser.
-//   (2) It is more time-efficient and space-efficient to have one
-//       x-Digest-Manifest instead of multiple x-Digest values.
-//
-// In order to get benefit (1), we do NOT implement the fallback to the older
-// mechanism as the spec requires/suggests. Also, for simplity's sake, we only
-// support exactly one SHA1-Digest-Manifest attribute, and no other
-// algorithms.
-//
-// filebuf must be null-terminated. On output, mfDigest will contain the
-// decoded value of SHA1-Digest-Manifest.
-nsresult
-ParseSF(const char* filebuf, /*out*/ SECItem & mfDigest)
-{
-  nsresult rv;
-
-  const char* nextLineStart = filebuf;
-  rv = CheckManifestVersion(nextLineStart, NS_LITERAL_CSTRING(JAR_SF_HEADER));
-  if (NS_FAILED(rv))
-    return rv;
-
-  // Find SHA1-Digest-Manifest
-  for (;;) {
-    nsAutoCString curLine;
-    rv = ReadLine(nextLineStart, curLine);
-    if (NS_FAILED(rv)) {
-      return rv;
-    }
-
-    if (curLine.Length() == 0) {
-      // End of main section (blank line or end-of-file), and no
-      // SHA1-Digest-Manifest found.
-      return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
-    }
-
-    nsAutoCString attrName;
-    nsAutoCString attrValue;
-    rv = ParseAttribute(curLine, attrName, attrValue);
-    if (NS_FAILED(rv)) {
-      return rv;
-    }
-
-    if (attrName.LowerCaseEqualsLiteral("sha1-digest-manifest")) {
-      rv = MapSECStatus(ATOB_ConvertAsciiToItem(&mfDigest, attrValue.get()));
-      if (NS_FAILED(rv)) {
-        return rv;
-      }
-
-      // There could be multiple SHA1-Digest-Manifest attributes, which
-      // would be an error, but it's better to just skip any erroneous
-      // duplicate entries rather than trying to detect them, because:
-      //
-      //   (1) It's simpler, and simpler generally means more secure
-      //   (2) An attacker can't make us accept a JAR we would otherwise
-      //       reject just by adding additional SHA1-Digest-Manifest
-      //       attributes.
-      break;
-    }
-
-    // ignore unrecognized attributes
-  }
-
-  return NS_OK;
-}
-
-// Parses MANIFEST.MF. The filenames of all entries will be returned in
-// mfItems. buf must be a pre-allocated scratch buffer that is used for doing
-// I/O.
-nsresult
-ParseMF(const char* filebuf, nsIZipReader * zip,
-        /*out*/ nsTHashtable<nsCStringHashKey> & mfItems,
-        ScopedAutoSECItem & buf)
-{
-  nsresult rv;
-
-  const char* nextLineStart = filebuf;
-
-  rv = CheckManifestVersion(nextLineStart, NS_LITERAL_CSTRING(JAR_MF_HEADER));
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-
-  // Skip the rest of the header section, which ends with a blank line.
-  {
-    nsAutoCString line;
-    do {
-      rv = ReadLine(nextLineStart, line);
-      if (NS_FAILED(rv)) {
-        return rv;
-      }
-    } while (line.Length() > 0);
-
-    // Manifest containing no file entries is OK, though useless.
-    if (*nextLineStart == '\0') {
-      return NS_OK;
-    }
-  }
-
-  nsAutoCString curItemName;
-  ScopedAutoSECItem digest;
-
-  for (;;) {
-    nsAutoCString curLine;
-    rv = ReadLine(nextLineStart, curLine);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    if (curLine.Length() == 0) {
-      // end of section (blank line or end-of-file)
-
-      if (curItemName.Length() == 0) {
-        // '...Each section must start with an attribute with the name as
-        // "Name",...', so every section must have a Name attribute.
-        return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
-      }
-
-      if (digest.len == 0) {
-        // We require every entry to have a digest, since we require every
-        // entry to be signed and we don't allow duplicate entries.
-        return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
-      }
-
-      if (mfItems.Contains(curItemName)) {
-        // Duplicate entry
-        return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
-      }
-
-      // Verify that the entry's content digest matches the digest from this
-      // MF section.
-      rv = VerifyEntryContentDigest(zip, curItemName, digest, buf);
-      if (NS_FAILED(rv))
-        return rv;
-
-      mfItems.PutEntry(curItemName);
-
-      if (*nextLineStart == '\0') // end-of-file
-        break;
-
-      // reset so we know we haven't encountered either of these for the next
-      // item yet.
-      curItemName.Truncate();
-      digest.reset();
-
-      continue; // skip the rest of the loop below
-    }
-
-    nsAutoCString attrName;
-    nsAutoCString attrValue;
-    rv = ParseAttribute(curLine, attrName, attrValue);
-    if (NS_FAILED(rv)) {
-      return rv;
-    }
-
-    // Lines to look for:
-
-    // (1) Digest:
-    if (attrName.LowerCaseEqualsLiteral("sha1-digest"))
-    {
-      if (digest.len > 0) // multiple SHA1 digests in section
-        return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
-
-      rv = MapSECStatus(ATOB_ConvertAsciiToItem(&digest, attrValue.get()));
-      if (NS_FAILED(rv))
-        return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
-
-      continue;
-    }
-
-    // (2) Name: associates this manifest section with a file in the jar.
-    if (attrName.LowerCaseEqualsLiteral("name"))
-    {
-      if (MOZ_UNLIKELY(curItemName.Length() > 0)) // multiple names in section
-        return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
-
-      if (MOZ_UNLIKELY(attrValue.Length() == 0))
-        return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
-
-      curItemName = attrValue;
-
-      continue;
-    }
-
-    // (3) Magic: the only other must-understand attribute
-    if (attrName.LowerCaseEqualsLiteral("magic")) {
-      // We don't understand any magic, so we can't verify an entry that
-      // requires magic. Since we require every entry to have a valid
-      // signature, we have no choice but to reject the entry.
-      return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
-    }
-
-    // unrecognized attributes must be ignored
-  }
-
-  return NS_OK;
-}
-
-// Callback functions for decoder. For now, use empty/default functions.
-void
-ContentCallback(void *arg, const char *buf, unsigned long len)
-{
-}
-PK11SymKey *
-GetDecryptKeyCallback(void *, SECAlgorithmID *)
-{
-  return nullptr;
-}
-PRBool
-DecryptionAllowedCallback(SECAlgorithmID *algid, PK11SymKey *bulkkey)
-{
-  return false;
-}
-void *
-GetPasswordKeyCallback(void *arg, void *handle)
-{
-  return nullptr;
-}
-
-NS_IMETHODIMP
-OpenSignedJARFile(nsIFile * aJarFile,
-                  /*out, optional */ nsIZipReader ** aZipReader,
-                  /*out, optional */ nsIX509Cert3 ** aSignerCert)
-{
-  NS_ENSURE_ARG_POINTER(aJarFile);
-
-  if (aZipReader) {
-    *aZipReader = nullptr;
-  }
-
-  if (aSignerCert) {
-    *aSignerCert = nullptr;
-  }
-
-  nsresult rv;
-
-  static NS_DEFINE_CID(kZipReaderCID, NS_ZIPREADER_CID);
-  nsCOMPtr<nsIZipReader> zip = do_CreateInstance(kZipReaderCID, &rv);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  rv = zip->Open(aJarFile);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  // Signature (RSA) file
-  nsAutoCString sigFilename;
-  ScopedAutoSECItem sigBuffer;
-  rv = FindAndLoadOneEntry(zip, nsLiteralCString(JAR_RSA_SEARCH_STRING),
-                           sigFilename, sigBuffer, nullptr);
-  if (NS_FAILED(rv)) {
-    return NS_ERROR_SIGNED_JAR_NOT_SIGNED;
-  }
-
-  sigBuffer.type = siBuffer;
-  ScopedSEC_PKCS7ContentInfo p7_info(SEC_PKCS7DecodeItem(&sigBuffer,
-                                        ContentCallback, nullptr,
-                                        GetPasswordKeyCallback, nullptr,
-                                        GetDecryptKeyCallback, nullptr,
-                                        DecryptionAllowedCallback));
-  if (!p7_info) {
-    PRErrorCode error = PR_GetError();
-    const char * errorName = PR_ErrorToName(error);
-    PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("Failed to decode PKCS#7 item: %s",
-           errorName));
-    return PRErrorCode_to_nsresult(error);
-  }
-
-  // Signature (SF) file
-  nsAutoCString sfFilename;
-  ScopedAutoSECItem sfBuffer;
-  Digest sfCalculatedDigest;
-  rv = FindAndLoadOneEntry(zip, NS_LITERAL_CSTRING(JAR_SF_SEARCH_STRING),
-                           sfFilename, sfBuffer, &sfCalculatedDigest);
-  if (NS_FAILED(rv)) {
-    return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
-  }
-
-  // Verify that the signature file is a valid signature of the SF file
-  if (!SEC_PKCS7VerifyDetachedSignatureAtTime(p7_info, certUsageObjectSigner,
-                                              &sfCalculatedDigest.get(),
-                                              HASH_AlgSHA1, false, PR_Now())) {
-    PRErrorCode error = PR_GetError();
-    const char * errorName = PR_ErrorToName(error);
-    PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("Failed to verify detached signature: %s",
-           errorName));
-    rv = PRErrorCode_to_nsresult(error);
-    return rv;
-  }
-
-  ScopedAutoSECItem mfDigest;
-  rv = ParseSF(char_ptr_cast(sfBuffer.data), mfDigest);
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-
-  // Manifest (MF) file
-  nsAutoCString mfFilename;
-  ScopedAutoSECItem manifestBuffer;
-  Digest mfCalculatedDigest;
-  rv = FindAndLoadOneEntry(zip, NS_LITERAL_CSTRING(JAR_MF_SEARCH_STRING),
-                           mfFilename, manifestBuffer, &mfCalculatedDigest);
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-
-  if (SECITEM_CompareItem(&mfDigest, &mfCalculatedDigest.get()) != SECEqual) {
-    return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
-  }
-
-  // Allocate the I/O buffer only once per JAR, instead of once per entry, in
-  // order to minimize malloc/free calls and in order to avoid fragmenting
-  // memory.
-  ScopedAutoSECItem buf(128 * 1024);
-
-  nsTHashtable<nsCStringHashKey> items;
-
-  rv = ParseMF(char_ptr_cast(manifestBuffer.data), zip, items, buf);
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-
-  // Verify every entry in the file.
-  nsCOMPtr<nsIUTF8StringEnumerator> entries;
-  rv = zip->FindEntries(EmptyCString(), getter_AddRefs(entries));
-  if (NS_SUCCEEDED(rv) && !entries) {
-    rv = NS_ERROR_UNEXPECTED;
-  }
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-
-  for (;;) {
-    bool hasMore;
-    rv = entries->HasMore(&hasMore);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    if (!hasMore) {
-      break;
-    }
-
-    nsAutoCString entryFilename;
-    rv = entries->GetNext(entryFilename);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("Verifying digests for %s",
-           entryFilename.get()));
-
-    // The files that comprise the signature mechanism are not covered by the
-    // signature.
-    //
-    // XXX: This is OK for a single signature, but doesn't work for
-    // multiple signatures, because the metadata for the other signatures
-    // is not signed either.
-    if (entryFilename == mfFilename ||
-        entryFilename == sfFilename ||
-        entryFilename == sigFilename) {
-      continue;
-    }
-
-    if (entryFilename.Length() == 0) {
-      return NS_ERROR_SIGNED_JAR_ENTRY_INVALID;
-    }
-
-    // Entries with names that end in "/" are directory entries, which are not
-    // signed.
-    //
-    // XXX: As long as we don't unpack the JAR into the filesystem, the "/"
-    // entries are harmless. But, it is not clear what the security
-    // implications of directory entries are if/when we were to unpackage the
-    // JAR into the filesystem.
-    if (entryFilename[entryFilename.Length() - 1] == '/') {
-      continue;
-    }
-
-    nsCStringHashKey * item = items.GetEntry(entryFilename);
-    if (!item) {
-      return NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY;
-    }
-
-    // Remove the item so we can check for leftover items later
-    items.RemoveEntry(entryFilename);
-  }
-
-  // We verified that every entry that we require to be signed is signed. But,
-  // were there any missing entries--that is, entries that are mentioned in the
-  // manifest but missing from the archive?
-  if (items.Count() != 0) {
-    return NS_ERROR_SIGNED_JAR_ENTRY_MISSING;
-  }
-
-  // Return the reader to the caller if they want it
-  if (aZipReader) {
-    zip.forget(aZipReader);
-  }
-
-  // Return the signer's certificate to the reader if they want it.
-  // XXX: We should return an nsIX509CertList with the whole validated chain,
-  //      but we can't do that until we switch to libpkix.
-  if (aSignerCert) {
-    CERTCertificate *rawSignerCert
-      = p7_info->content.signedData->signerInfos[0]->cert;
-    NS_ENSURE_TRUE(rawSignerCert, NS_ERROR_UNEXPECTED);
-
-    nsCOMPtr<nsIX509Cert3> signerCert = nsNSSCertificate::Create(rawSignerCert);
-    NS_ENSURE_TRUE(signerCert, NS_ERROR_OUT_OF_MEMORY);
-
-    signerCert.forget(aSignerCert);
-  }
-
-  return NS_OK;
-}
-
-class OpenSignedJARFileTask MOZ_FINAL : public CryptoTask
-{
-public:
-  OpenSignedJARFileTask(nsIFile * aJarFile,
-                        nsIOpenSignedJARFileCallback * aCallback)
-    : mJarFile(aJarFile)
-    , mCallback(new nsMainThreadPtrHolder<nsIOpenSignedJARFileCallback>(aCallback))
-  {
-  }
-
-private:
-  virtual nsresult CalculateResult() MOZ_OVERRIDE
-  {
-    return OpenSignedJARFile(mJarFile, getter_AddRefs(mZipReader),
-                             getter_AddRefs(mSignerCert));
-  }
-
-  // nsNSSCertificate implements nsNSSShutdownObject, so there's nothing that
-  // needs to be released
-  virtual void ReleaseNSSResources() { }
-
-  virtual void CallCallback(nsresult rv)
-  {
-    (void) mCallback->OpenSignedJARFileFinished(rv, mZipReader, mSignerCert);
-  }
-
-  const nsCOMPtr<nsIFile> mJarFile;
-  nsMainThreadPtrHandle<nsIOpenSignedJARFileCallback> mCallback;
-  nsCOMPtr<nsIZipReader> mZipReader; // out
-  nsCOMPtr<nsIX509Cert3> mSignerCert; // out
-};
-
-} // unnamed namespace
-
-NS_IMETHODIMP
-nsNSSCertificateDB::OpenSignedJARFileAsync(
-  nsIFile * aJarFile, nsIOpenSignedJARFileCallback * aCallback)
-{
-  NS_ENSURE_ARG_POINTER(aJarFile);
-  NS_ENSURE_ARG_POINTER(aCallback);
-  RefPtr<OpenSignedJARFileTask> task(new OpenSignedJARFileTask(aJarFile,
-                                                               aCallback));
-  return task->Dispatch("SignedJAR");
-}
--- a/security/manager/ssl/src/moz.build
+++ b/security/manager/ssl/src/moz.build
@@ -63,17 +63,16 @@ UNIFIED_SOURCES += [
     'SSLServerCertVerification.cpp',
     'TransportSecurityInfo.cpp',
 ]
 
 # nsNSSCertificateDB.cpp needs to include nscert.h before everything else.
 # The rest cannot be built in unified mode because they want to force NSPR
 # logging.
 SOURCES += [
-    'JARSignatureVerification.cpp',
     'nsCryptoHash.cpp',
     'nsNSSCertificateDB.cpp',
     'nsNSSComponent.cpp',
     'nsNSSVersion.cpp',
     'PSMContentListener.cpp',
 ]
 
 if not CONFIG['MOZ_DISABLE_CRYPTOLEGACY']:
--- a/security/manager/ssl/src/nsCrypto.cpp
+++ b/security/manager/ssl/src/nsCrypto.cpp
@@ -65,16 +65,17 @@
 #include "pk11pqg.h"
 #include "cmmf.h"
 #include "nssb64.h"
 #include "base64.h"
 #include "cert.h"
 #include "certdb.h"
 #include "secmod.h"
 #include "ScopedNSSTypes.h"
+#include "insanity/pkixtypes.h"
 
 #include "ssl.h" // For SSL_ClearSessionCache
 
 #include "nsNSSCleaner.h"
 
 #include "nsNSSCertHelper.h"
 #include <algorithm>
 #include "nsWrapperCacheInlines.h"
@@ -2379,19 +2380,20 @@ nsCrypto::ImportUserCertificates(const n
     CMMF_DestroyCertResponse(currResponse);
   }
   //Let the loser: label take care of freeing up our reference to
   //nickname (This way we don't free it twice and avoid crashing.
   //That would be a good thing.
 
   //Import the root chain into the cert db.
  {
-  ScopedCERTCertList caPubs(CMMF_CertRepContentGetCAPubs(certRepContent));
+  insanity::pkix::ScopedCERTCertList
+    caPubs(CMMF_CertRepContentGetCAPubs(certRepContent));
   if (caPubs) {
-    int32_t numCAs = nsCertListCount(caPubs);
+    int32_t numCAs = nsCertListCount(caPubs.get());
     
     NS_ASSERTION(numCAs > 0, "Invalid number of CA's");
     if (numCAs > 0) {
       CERTCertListNode *node;
       SECItem *derCerts;
 
       derCerts = static_cast<SECItem*>
                             (nsMemory::Alloc(sizeof(SECItem)*numCAs));
--- a/security/manager/ssl/tests/unit/head_psm.js
+++ b/security/manager/ssl/tests/unit/head_psm.js
@@ -17,16 +17,17 @@ let gIsWindows = ("@mozilla.org/windows-
 
 const isDebugBuild = Cc["@mozilla.org/xpcom/debug;1"]
                        .getService(Ci.nsIDebug2).isDebugBuild;
 
 const SEC_ERROR_BASE = Ci.nsINSSErrorsService.NSS_SEC_ERROR_BASE;
 
 // Sort in numerical order
 const SEC_ERROR_REVOKED_CERTIFICATE                     = SEC_ERROR_BASE +  12;
+const SEC_ERROR_UNKNOWN_ISSUER                          = SEC_ERROR_BASE +  13;
 const SEC_ERROR_BAD_DATABASE                            = SEC_ERROR_BASE +  18;
 const SEC_ERROR_UNTRUSTED_ISSUER                        = SEC_ERROR_BASE +  20;
 const SEC_ERROR_EXTENSION_NOT_FOUND                     = SEC_ERROR_BASE +  35;
 const SEC_ERROR_OCSP_MALFORMED_REQUEST                  = SEC_ERROR_BASE + 120;
 const SEC_ERROR_OCSP_SERVER_ERROR                       = SEC_ERROR_BASE + 121;
 const SEC_ERROR_OCSP_TRY_SERVER_LATER                   = SEC_ERROR_BASE + 122;
 const SEC_ERROR_OCSP_REQUEST_NEEDS_SIG                  = SEC_ERROR_BASE + 123;
 const SEC_ERROR_OCSP_UNAUTHORIZED_REQUEST               = SEC_ERROR_BASE + 124;
--- a/security/manager/ssl/tests/unit/test_signed_apps-marketplace.js
+++ b/security/manager/ssl/tests/unit/test_signed_apps-marketplace.js
@@ -5,56 +5,39 @@ const isB2G = ("@mozilla.org/b2g-process
 
 do_get_profile(); // must be called before getting nsIX509CertDB
 const certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(Ci.nsIX509CertDB);
 
 function run_test() {
   run_next_test();
 }
 
-// XXX: NSS has many possible error codes for this, e.g.
-// SEC_ERROR_UNTRUSTED_ISSUER and others are also reasonable. Future
-// versions of NSS may return one of these alternate errors; in that case
-// we need to update this test.
-//
-// XXX (bug 812089): Cr.NS_ERROR_SEC_ERROR_UNKNOWN_ISSUER is undefined.
-//
-// XXX: Cannot use operator| instead of operator+ to combine bits because
-// bit 31 trigger's JavaScript's crazy interpretation of the numbers as
-// two's complement negative integers.
-const NS_ERROR_SEC_ERROR_UNKNOWN_ISSUER = 0x80000000 /*unsigned (1 << 31)*/
-				        + (    (0x45 + 21) << 16)
-				        + (-(-0x2000 + 13)      );
-
 function check_open_result(name, expectedRv) {
-  if (expectedRv == Cr.NS_OK && !isB2G) {
-    // We do not trust the marketplace trust anchor on non-B2G builds
-    expectedRv = NS_ERROR_SEC_ERROR_UNKNOWN_ISSUER;
-  }
-
-  return function openSignedJARFileCallback(rv, aZipReader, aSignerCert) {
-    do_print("openSignedJARFileCallback called for " + name);
+  return function openSignedAppFileCallback(rv, aZipReader, aSignerCert) {
+    do_print("openSignedAppFileCallback called for " + name);
     do_check_eq(rv, expectedRv);
     do_check_eq(aZipReader != null,  Components.isSuccessCode(expectedRv));
     do_check_eq(aSignerCert != null, Components.isSuccessCode(expectedRv));
     run_next_test();
   };
 }
 
 function original_app_path(test_name) {
   return do_get_file("test_signed_apps/" + test_name + ".zip", false);
 }
 
 // Test that we no longer trust the test root cert that was originally used
 // during development of B2G 1.0.
 add_test(function () {
-  certdb.openSignedJARFileAsync(
+  certdb.openSignedAppFileAsync(
+    Ci.nsIX509CertDB.AppMarketplaceProdPublicRoot,
     original_app_path("test-privileged-app-test-1.0"),
     check_open_result("test-privileged-app-test-1.0",
-                      NS_ERROR_SEC_ERROR_UNKNOWN_ISSUER));
+                      getXPCOMStatusFromNSS(SEC_ERROR_UNKNOWN_ISSUER)));
 });
 
 // Test that we trust the root cert used by by the Firefox Marketplace.
 add_test(function () {
-  certdb.openSignedJARFileAsync(
+  certdb.openSignedAppFileAsync(
+    Ci.nsIX509CertDB.AppMarketplaceProdPublicRoot,
     original_app_path("privileged-app-test-1.0"),
     check_open_result("privileged-app-test-1.0", Cr.NS_OK));
 });
--- a/security/manager/ssl/tests/unit/test_signed_apps.js
+++ b/security/manager/ssl/tests/unit/test_signed_apps.js
@@ -106,127 +106,121 @@ function truncateEntry(entry, entryInput
   var content = Cc["@mozilla.org/io/string-input-stream;1"]
                   .createInstance(Ci.nsIStringInputStream);
   content.data = "";
 
   return [entry, content]
 }
 
 function run_test() {
-  var root_cert_der = 
-    do_get_file("test_signed_apps/trusted_ca1.der", false);
-  var der = readFile(root_cert_der);
-  certdb.addCert(der, ",,CTu", "test-root");
   run_next_test();
 }
 
 function check_open_result(name, expectedRv) {
-  return function openSignedJARFileCallback(rv, aZipReader, aSignerCert) {
-    do_print("openSignedJARFileCallback called for " + name);
+  return function openSignedAppFileCallback(rv, aZipReader, aSignerCert) {
+    do_print("openSignedAppFileCallback called for " + name);
     do_check_eq(rv, expectedRv);
     do_check_eq(aZipReader != null,  Components.isSuccessCode(expectedRv));
     do_check_eq(aSignerCert != null, Components.isSuccessCode(expectedRv));
     run_next_test();
   };
 }
 
 function original_app_path(test_name) {
   return do_get_file("test_signed_apps/" + test_name + ".zip", false);
 }
 
 function tampered_app_path(test_name) {
   return FileUtils.getFile("TmpD", ["test_signed_app-" + test_name + ".zip"]);
 }
 
 add_test(function () {
-  certdb.openSignedJARFileAsync(original_app_path("valid"),
-                                check_open_result("valid", Cr.NS_OK));
-});
-
-add_test(function () {
-  certdb.openSignedJARFileAsync(original_app_path("unsigned"),
-             check_open_result("unsigned", Cr.NS_ERROR_SIGNED_JAR_NOT_SIGNED));
+  certdb.openSignedAppFileAsync(
+    Ci.nsIX509CertDB.AppXPCShellRoot, original_app_path("valid"),
+    check_open_result("valid", Cr.NS_OK));
 });
 
 add_test(function () {
-  // XXX: NSS has many possible error codes for this, e.g.
-  // SEC_ERROR_UNTRUSTED_ISSUER and others are also reasonable. Future versions
-  // of NSS may return one of these alternate errors; in that case, we need to
-  // update this test.
-  //
-  // XXX (bug 812089): Cr.NS_ERROR_SEC_ERROR_UNKNOWN_ISSUER is undefined.
-  //
-  // XXX: Cannot use operator| instead of operator+ to combine bits because
-  // bit 31 trigger's JavaScript's crazy interpretation of the numbers as
-  // two's complement negative integers.
-  const NS_ERROR_SEC_ERROR_UNKNOWN_ISSUER = 0x80000000 /* unsigned (1 << 31) */
-                                          + (    (0x45 + 21) << 16)
-                                          + (-(-0x2000 + 13)      );
-  certdb.openSignedJARFileAsync(original_app_path("unknown_issuer"),
+  certdb.openSignedAppFileAsync(
+    Ci.nsIX509CertDB.AppXPCShellRoot, original_app_path("unsigned"),
+    check_open_result("unsigned", Cr.NS_ERROR_SIGNED_JAR_NOT_SIGNED));
+});
+
+add_test(function () {
+  certdb.openSignedAppFileAsync(
+    Ci.nsIX509CertDB.AppXPCShellRoot, original_app_path("unknown_issuer"),
     check_open_result("unknown_issuer",
-                      /*Cr.*/NS_ERROR_SEC_ERROR_UNKNOWN_ISSUER));
+                      getXPCOMStatusFromNSS(SEC_ERROR_UNKNOWN_ISSUER)));
 });
 
 // Sanity check to ensure a no-op tampering gives a valid result
 add_test(function () {
   var tampered = tampered_app_path("identity_tampering");
   tamper(original_app_path("valid"), tampered, { }, []);
-  certdb.openSignedJARFileAsync(original_app_path("valid"),
+  certdb.openSignedAppFileAsync(
+    Ci.nsIX509CertDB.AppXPCShellRoot, original_app_path("valid"),
     check_open_result("identity_tampering", Cr.NS_OK));
 });
 
 add_test(function () {
   var tampered = tampered_app_path("missing_rsa");
   tamper(original_app_path("valid"), tampered, { "META-INF/A.RSA" : removeEntry }, []);
-  certdb.openSignedJARFileAsync(tampered,
+  certdb.openSignedAppFileAsync(
+    Ci.nsIX509CertDB.AppXPCShellRoot, tampered,
     check_open_result("missing_rsa", Cr.NS_ERROR_SIGNED_JAR_NOT_SIGNED));
 });
 
 add_test(function () {
   var tampered = tampered_app_path("missing_sf");
   tamper(original_app_path("valid"), tampered, { "META-INF/A.SF" : removeEntry }, []);
-  certdb.openSignedJARFileAsync(tampered,
+  certdb.openSignedAppFileAsync(
+    Ci.nsIX509CertDB.AppXPCShellRoot, tampered,
     check_open_result("missing_sf", Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID));
 });
 
 add_test(function () {
   var tampered = tampered_app_path("missing_manifest_mf");
   tamper(original_app_path("valid"), tampered, { "META-INF/MANIFEST.MF" : removeEntry }, []);
-  certdb.openSignedJARFileAsync(tampered,
+  certdb.openSignedAppFileAsync(
+    Ci.nsIX509CertDB.AppXPCShellRoot, tampered,
     check_open_result("missing_manifest_mf",
                       Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID));
 });
 
 add_test(function () {
   var tampered = tampered_app_path("missing_entry");
   tamper(original_app_path("valid"), tampered, { "manifest.webapp" : removeEntry }, []);
-  certdb.openSignedJARFileAsync(tampered,
-      check_open_result("missing_entry", Cr.NS_ERROR_SIGNED_JAR_ENTRY_MISSING));
+  certdb.openSignedAppFileAsync(
+    Ci.nsIX509CertDB.AppXPCShellRoot, tampered,
+    check_open_result("missing_entry", Cr.NS_ERROR_SIGNED_JAR_ENTRY_MISSING));
 });
 
 add_test(function () {
   var tampered = tampered_app_path("truncated_entry");
   tamper(original_app_path("valid"), tampered, { "manifest.webapp" : truncateEntry }, []);
-  certdb.openSignedJARFileAsync(tampered,
+  certdb.openSignedAppFileAsync(
+    Ci.nsIX509CertDB.AppXPCShellRoot, tampered,
     check_open_result("truncated_entry", Cr.NS_ERROR_SIGNED_JAR_MODIFIED_ENTRY));
 });
 
 add_test(function () {
   var tampered = tampered_app_path("unsigned_entry");
   tamper(original_app_path("valid"), tampered, {},
     [ { "name": "unsigned.txt", "content": "unsigned content!" } ]);
-  certdb.openSignedJARFileAsync(tampered,
+  certdb.openSignedAppFileAsync(
+    Ci.nsIX509CertDB.AppXPCShellRoot, tampered,
     check_open_result("unsigned_entry", Cr.NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY));
 });
 
 add_test(function () {
   var tampered = tampered_app_path("unsigned_metainf_entry");
   tamper(original_app_path("valid"), tampered, {},
     [ { name: "META-INF/unsigned.txt", content: "unsigned content!" } ]);
-  certdb.openSignedJARFileAsync(tampered,
+  certdb.openSignedAppFileAsync(
+    Ci.nsIX509CertDB.AppXPCShellRoot, tampered,
     check_open_result("unsigned_metainf_entry",
                       Cr.NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY));
 });
 
 // TODO: tampered MF, tampered SF
 // TODO: too-large MF, too-large RSA, too-large SF
 // TODO: MF and SF that end immediately after the last main header
 //       (no CR nor LF)
--- a/toolkit/toolkit.mozbuild
+++ b/toolkit/toolkit.mozbuild
@@ -8,16 +8,19 @@ if CONFIG['LIBXUL_SDK']:
 
 if CONFIG['MOZ_CONTENT_SANDBOX']:
     add_tier_dir('sandbox', 'security/sandbox')
 
 # Depends on NSS and NSPR, and must be built after sandbox or else B2G emulator
 # builds fail.
 add_tier_dir('platform', 'security/certverifier')
 
+# Depends on certverifier
+add_tier_dir('platform', 'security/apps')
+
 # the signing related bits of libmar depend on nss
 if CONFIG['MOZ_UPDATER']:
     add_tier_dir('platform', 'modules/libmar')
 
 if CONFIG['NS_TRACE_MALLOC']:
     add_tier_dir('platform', 'tools/trace-malloc/lib')
 
 if CONFIG['MOZ_DMD']: