Bug 896620: Make marketplace certs work on in all products, r=keeler, r=briansmith, a=sledru
authorMarco Castelluccio <mar.castelluccio@studenti.unina.it>
Tue, 18 Feb 2014 13:33:08 -0800
changeset 182910 0f5364659f88f5e763b932944a024707b2991d67
parent 182909 b0b52ea69cec5481c3529983c127cf06c510765b
child 182911 5a6283bb2a78b34911174a8606daa7519dfb7d98
push id3343
push userffxbld
push dateMon, 17 Mar 2014 21:55:32 +0000
treeherdermozilla-beta@2f7d3415f79f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskeeler, briansmith, sledru
bugs896620
milestone29.0a2
Bug 896620: Make marketplace certs work on in all products, r=keeler, r=briansmith, a=sledru
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
@@ -3077,18 +3077,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 MOZ_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
@@ -19,16 +19,19 @@ if CONFIG['COMPILE_ENVIRONMENT']:
 
 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']: