bug 1180826 - add support for sha256 digests in add-on signature manifests r?jcj,dveditz draft
authorDavid Keeler <dkeeler@mozilla.com>
Mon, 09 Oct 2017 13:53:23 -0700
changeset 684999 87c4b6f2fde536063cb0be6a5f3d5ae2d1d56e42
parent 684703 d49501f258b105c5e2dcd0a59896ec1ceabf726b
child 737025 665061a276e45b863868e79584c1f9c2aeb01ce7
push id85795
push userbmo:dkeeler@mozilla.com
push dateMon, 23 Oct 2017 21:49:37 +0000
reviewersjcj, dveditz
bugs1180826
milestone58.0a1
bug 1180826 - add support for sha256 digests in add-on signature manifests r?jcj,dveditz MozReview-Commit-ID: HTlm6esgPUx
security/apps/AppSignatureVerification.cpp
security/manager/ssl/tests/unit/sign_app.py
security/manager/ssl/tests/unit/test_signed_apps.js
security/manager/ssl/tests/unit/test_signed_apps/moz.build
security/manager/ssl/tests/unit/test_signed_apps/sha1_and_sha256_manifest_sha1_signature_file.zip
security/manager/ssl/tests/unit/test_signed_apps/sha1_and_sha256_manifest_sha256_signature_file.zip
security/manager/ssl/tests/unit/test_signed_apps/sha1_manifest_sha1_and_sha256_signature_file.zip
security/manager/ssl/tests/unit/test_signed_apps/sha256_manifest_sha1_and_sha256_signature_file.zip
security/manager/ssl/tests/unit/test_signed_apps/signed_app.zip
security/manager/ssl/tests/unit/test_signed_apps/signed_app_sha1_and_sha256.zip
security/manager/ssl/tests/unit/test_signed_apps/signed_app_sha256.zip
security/manager/ssl/tests/unit/test_signed_apps/signed_app_sha256_manifest.zip
security/manager/ssl/tests/unit/test_signed_apps/signed_app_sha256_signature_file.zip
security/manager/ssl/tests/unit/test_signed_apps/unknown_issuer_app.zip
security/manager/ssl/tests/unit/test_signed_apps/unsigned_app.zip
--- a/security/apps/AppSignatureVerification.cpp
+++ b/security/apps/AppSignatureVerification.cpp
@@ -41,16 +41,45 @@
 using namespace mozilla::pkix;
 using namespace mozilla;
 using namespace mozilla::psm;
 
 extern mozilla::LazyLogModule gPIPNSSLog;
 
 namespace {
 
+// A convenient way to pair the bytes of a digest with the algorithm that
+// purportedly produced those bytes. Only SHA-1 and SHA-256 are supported.
+struct DigestWithAlgorithm
+{
+  nsresult ValidateLength() const
+  {
+    size_t hashLen;
+    switch (mAlgorithm) {
+      case SEC_OID_SHA256:
+        hashLen = SHA256_LENGTH;
+        break;
+      case SEC_OID_SHA1:
+        hashLen = SHA1_LENGTH;
+        break;
+      default:
+        MOZ_ASSERT_UNREACHABLE(
+          "unsupported hash type in DigestWithAlgorithm::ValidateLength");
+        return NS_ERROR_FAILURE;
+    }
+    if (mDigest.Length() != hashLen) {
+      return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
+    }
+    return NS_OK;
+  }
+
+  nsAutoCString mDigest;
+  SECOidTag mAlgorithm;
+};
+
 // The digest must have a lifetime greater than or equal to the returned string.
 inline nsDependentCSubstring
 DigestToDependentString(const Digest& digest)
 {
   return nsDependentCSubstring(
     BitwiseCast<char*, unsigned char*>(digest.get().data),
     digest.get().len);
 }
@@ -107,23 +136,25 @@ ReadStream(const nsCOMPtr<nsIInputStream
   buf.data[buf.len - 1] = 0; // null-terminate
 
   return NS_OK;
 }
 
 // Finds exactly one (signature metadata) JAR 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.
+// bufDigest will contain the digeset of the entry using the given digest
+// algorithm.
 nsresult
-FindAndLoadOneEntry(nsIZipReader * zip,
-                    const nsACString & searchPattern,
-                    /*out*/ nsACString & filename,
-                    /*out*/ SECItem & buf,
-                    /*optional, out*/ Digest * bufDigest)
+FindAndLoadOneEntry(nsIZipReader* zip,
+                    const nsACString& searchPattern,
+                    /*out*/ nsACString& filename,
+                    /*out*/ SECItem& buf,
+                    /*optional, in*/ SECOidTag digestAlgorithm = SEC_OID_SHA1,
+                    /*optional, out*/ Digest* bufDigest = nullptr)
 {
   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;
@@ -148,17 +179,17 @@ FindAndLoadOneEntry(nsIZipReader * zip,
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = ReadStream(stream, buf);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return NS_ERROR_SIGNED_JAR_ENTRY_INVALID;
   }
 
   if (bufDigest) {
-    rv = bufDigest->DigestBuf(SEC_OID_SHA1, buf.data, buf.len - 1);
+    rv = bufDigest->DigestBuf(digestAlgorithm, 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
@@ -168,32 +199,34 @@ FindAndLoadOneEntry(nsIZipReader * zip,
 //                it is from a signed archive or unpacked into a directory
 // @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
 VerifyStreamContentDigest(nsIInputStream* stream,
-                          const nsCString& digestFromManifest, SECItem& buf)
+                          const DigestWithAlgorithm& digestFromManifest,
+                          SECItem& buf)
 {
   MOZ_ASSERT(buf.len > 0);
-  if (digestFromManifest.Length() != SHA1_LENGTH) {
-    return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
+  nsresult rv = digestFromManifest.ValidateLength();
+  if (NS_FAILED(rv)) {
+    return rv;
   }
 
-  nsresult rv;
   uint64_t len64;
   rv = stream->Available(&len64);
   NS_ENSURE_SUCCESS(rv, rv);
   if (len64 > UINT32_MAX) {
     return NS_ERROR_SIGNED_JAR_ENTRY_TOO_LARGE;
   }
 
-  UniquePK11Context digestContext(PK11_CreateDigestContext(SEC_OID_SHA1));
+  UniquePK11Context digestContext(
+    PK11_CreateDigestContext(digestFromManifest.mAlgorithm));
   if (!digestContext) {
     return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
   }
 
   rv = MapSECStatus(PK11_DigestBegin(digestContext.get()));
   NS_ENSURE_SUCCESS(rv, rv);
 
   uint64_t totalBytesRead = 0;
@@ -219,30 +252,31 @@ VerifyStreamContentDigest(nsIInputStream
   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);
+  rv = digest.End(digestFromManifest.mAlgorithm, digestContext);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsDependentCSubstring digestStr(DigestToDependentString(digest));
-  if (!digestStr.Equals(digestFromManifest)) {
+  if (!digestStr.Equals(digestFromManifest.mDigest)) {
     return NS_ERROR_SIGNED_JAR_MODIFIED_ENTRY;
   }
 
   return NS_OK;
 }
 
 nsresult
 VerifyEntryContentDigest(nsIZipReader* zip, const nsACString& aFilename,
-                         const nsCString& digestFromManifest, SECItem& buf)
+                         const DigestWithAlgorithm& digestFromManifest,
+                         SECItem& buf)
 {
   nsCOMPtr<nsIInputStream> stream;
   nsresult rv = zip->GetInputStream(aFilename, getter_AddRefs(stream));
   if (NS_FAILED(rv)) {
     return NS_ERROR_SIGNED_JAR_ENTRY_MISSING;
   }
 
   return VerifyStreamContentDigest(stream, digestFromManifest, buf);
@@ -250,17 +284,18 @@ VerifyEntryContentDigest(nsIZipReader* z
 
 // @oaram aDir       directory containing the unpacked signed archive
 // @param aFilename  path of the target file relative to aDir
 // @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
 nsresult
 VerifyFileContentDigest(nsIFile* aDir, const nsAString& aFilename,
-                        const nsCString& digestFromManifest, SECItem& buf)
+                        const DigestWithAlgorithm& digestFromManifest,
+                        SECItem& buf)
 {
   // Find the file corresponding to the manifest path
   nsCOMPtr<nsIFile> file;
   nsresult rv = aDir->Clone(getter_AddRefs(file));
   if (NS_FAILED(rv)) {
     return rv;
   }
 
@@ -423,105 +458,132 @@ CheckManifestVersion(const char* & nextL
     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.
+// Parses a signature file (SF) based on 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 SF file must contain a SHA1-Digest-Manifest (or, preferrably,
+// SHA256-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.
+// decoded value of SHA1-Digest-Manifest or SHA256-Digest-Manifest, if found,
+// as well as an identifier indicating which algorithm was found.
 nsresult
-ParseSF(const char* filebuf, /*out*/ nsCString& mfDigest)
+ParseSF(const char* filebuf, /*out*/ DigestWithAlgorithm& mfDigest)
 {
   const char* nextLineStart = filebuf;
   nsresult rv = CheckManifestVersion(nextLineStart,
                                      NS_LITERAL_CSTRING(JAR_SF_HEADER));
   if (NS_FAILED(rv)) {
     return rv;
   }
 
-  // Find SHA1-Digest-Manifest
+  nsAutoCString savedSHA1Digest;
+  // Search for SHA256-Digest-Manifest and SHA1-Digest-Manifest. Prefer the
+  // former.
   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.
+      // End of main section (blank line or end-of-file). We didn't find
+      // SHA256-Digest-Manifest, but maybe we found SHA1-Digest-Manifest.
+      if (!savedSHA1Digest.IsEmpty()) {
+        mfDigest.mDigest.Assign(savedSHA1Digest);
+        mfDigest.mAlgorithm = SEC_OID_SHA1;
+        return NS_OK;
+      }
       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 = Base64Decode(attrValue, mfDigest);
+    if (attrName.LowerCaseEqualsLiteral("sha256-digest-manifest")) {
+      rv = Base64Decode(attrValue, mfDigest.mDigest);
       if (NS_FAILED(rv)) {
         return rv;
       }
+      mfDigest.mAlgorithm = SEC_OID_SHA256;
 
-      // There could be multiple SHA1-Digest-Manifest attributes, which
+      // There could be multiple SHA*-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
+      //       reject just by adding additional SHA*-Digest-Manifest
       //       attributes.
-      break;
+      return NS_OK;
+    }
+
+    if (attrName.LowerCaseEqualsLiteral("sha1-digest-manifest") &&
+        savedSHA1Digest.IsEmpty()) {
+      rv = Base64Decode(attrValue, savedSHA1Digest);
+      if (NS_FAILED(rv)) {
+        return rv;
+      }
     }
 
     // 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.
+// I/O. Each file's contents are verified against the entry in the manifest with
+// the digest algorithm that matches the given one. This algorithm comes from
+// the signature file. If the signature file has a SHA-256 digest, then SHA-256
+// entries must be present in the manifest file. If the signature file only has
+// a SHA-1 digest, then only SHA-1 digests will be used in the manifest file.
 nsresult
-ParseMF(const char* filebuf, nsIZipReader * zip,
-        /*out*/ nsTHashtable<nsCStringHashKey> & mfItems,
-        ScopedAutoSECItem & buf)
+ParseMF(const char* filebuf, nsIZipReader* zip, SECOidTag digestAlgorithm,
+        /*out*/ nsTHashtable<nsCStringHashKey>& mfItems, ScopedAutoSECItem& buf)
 {
-  nsresult rv;
+  const char* digestNameToFind = nullptr;
+  switch (digestAlgorithm) {
+    case SEC_OID_SHA256:
+      digestNameToFind = "sha256-digest";
+      break;
+    case SEC_OID_SHA1:
+      digestNameToFind = "sha1-digest";
+      break;
+    default:
+      MOZ_ASSERT_UNREACHABLE("bad argument to ParseMF");
+      return NS_ERROR_FAILURE;
+  }
 
   const char* nextLineStart = filebuf;
-
-  rv = CheckManifestVersion(nextLineStart, NS_LITERAL_CSTRING(JAR_MF_HEADER));
+  nsresult 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 {
@@ -538,17 +600,19 @@ ParseMF(const char* filebuf, nsIZipReade
   }
 
   nsAutoCString curItemName;
   nsAutoCString digest;
 
   for (;;) {
     nsAutoCString curLine;
     rv = ReadLine(nextLineStart, curLine);
-    NS_ENSURE_SUCCESS(rv, rv);
+    if (NS_FAILED(rv)) {
+      return 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;
@@ -562,24 +626,28 @@ ParseMF(const char* filebuf, nsIZipReade
 
       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))
+      DigestWithAlgorithm digestWithAlgorithm = { digest, digestAlgorithm };
+      rv = VerifyEntryContentDigest(zip, curItemName, digestWithAlgorithm, buf);
+      if (NS_FAILED(rv)) {
         return rv;
+      }
 
       mfItems.PutEntry(curItemName);
 
-      if (*nextLineStart == '\0') // end-of-file
+      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.Truncate();
 
       continue; // skip the rest of the loop below
     }
@@ -589,33 +657,31 @@ ParseMF(const char* filebuf, nsIZipReade
     rv = ParseAttribute(curLine, attrName, attrValue);
     if (NS_FAILED(rv)) {
       return rv;
     }
 
     // Lines to look for:
 
     // (1) Digest:
-    if (attrName.LowerCaseEqualsLiteral("sha1-digest"))
-    {
-      if (!digest.IsEmpty()) { // multiple SHA1 digests in section
+    if (attrName.EqualsIgnoreCase(digestNameToFind)) {
+      if (!digest.IsEmpty()) { // multiple SHA* digests in section
         return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
       }
 
       rv = Base64Decode(attrValue, digest);
       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 (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;
 
@@ -711,17 +777,17 @@ VerifySignature(AppTrustedRoot trustedRo
   VerifyCertificateContext context = { trustedRoot, builtChain };
   // XXX: missing pinArg
   return VerifyCMSDetachedSignatureIncludingCertificate(buffer, detachedDigest,
                                                         VerifyCertificate,
                                                         &context, nullptr,
                                                         locker);
 }
 
-NS_IMETHODIMP
+nsresult
 OpenSignedAppFile(AppTrustedRoot aTrustedRoot, nsIFile* aJarFile,
                   /*out, optional */ nsIZipReader** aZipReader,
                   /*out, optional */ nsIX509Cert** aSignerCert)
 {
   NS_ENSURE_ARG_POINTER(aJarFile);
 
   if (aZipReader) {
     *aZipReader = nullptr;
@@ -739,70 +805,72 @@ OpenSignedAppFile(AppTrustedRoot aTruste
 
   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);
+                           sigFilename, sigBuffer);
   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);
+                           sfFilename, sfBuffer, SEC_OID_SHA1,
+                           &sfCalculatedDigest);
   if (NS_FAILED(rv)) {
     return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
   }
 
   sigBuffer.type = siBuffer;
   UniqueCERTCertList builtChain;
   rv = VerifySignature(aTrustedRoot, sigBuffer, sfCalculatedDigest.get(),
                        builtChain);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
-  nsAutoCString mfDigest;
+  DigestWithAlgorithm mfDigest;
   rv = ParseSF(BitwiseCast<char*, unsigned char*>(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);
+                           mfFilename, manifestBuffer, mfDigest.mAlgorithm,
+                           &mfCalculatedDigest);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   nsDependentCSubstring calculatedDigest(
     DigestToDependentString(mfCalculatedDigest));
-  if (!mfDigest.Equals(calculatedDigest)) {
+  if (!mfDigest.mDigest.Equals(calculatedDigest)) {
     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(BitwiseCast<char*, unsigned char*>(manifestBuffer.data), zip,
-               items, buf);
+               mfDigest.mAlgorithm, 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) {
@@ -988,22 +1056,24 @@ FindSignatureFilename(nsIFile* aMetaDir,
   }
 
   files->Close();
   return rv;
 }
 
 // Loads the signature metadata file that matches the given filename in
 // the passed-in Meta-inf directory. If bufDigest is not null then on
-// success bufDigest will contain the SHA-1 digest of the entry.
+// success bufDigest will contain the SHA1 or SHA256 digest of the entry
+// (depending on what aDigestAlgorithm is).
 nsresult
 LoadOneMetafile(nsIFile* aMetaDir,
                 const nsAString& aFilename,
                 /*out*/ SECItem& aBuf,
-                /*optional, out*/ Digest* aBufDigest)
+                /*optional, in*/ SECOidTag aDigestAlgorithm = SEC_OID_SHA1,
+                /*optional, out*/ Digest* aBufDigest = nullptr)
 {
   nsCOMPtr<nsIFile> metafile;
   nsresult rv = aMetaDir->Clone(getter_AddRefs(metafile));
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = metafile->Append(aFilename);
   NS_ENSURE_SUCCESS(rv, rv);
 
@@ -1019,37 +1089,48 @@ LoadOneMetafile(nsIFile* aMetaDir,
   rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), metafile);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = ReadStream(stream, aBuf);
   stream->Close();
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (aBufDigest) {
-    rv = aBufDigest->DigestBuf(SEC_OID_SHA1, aBuf.data, aBuf.len - 1);
+    rv = aBufDigest->DigestBuf(aDigestAlgorithm, aBuf.data, aBuf.len - 1);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   return NS_OK;
 }
 
 // Parses MANIFEST.MF and verifies the contents of the unpacked files
 // listed in the manifest.
 // The filenames of all entries will be returned in aMfItems. aBuf must
 // be a pre-allocated scratch buffer that is used for doing I/O.
 nsresult
-ParseMFUnpacked(const char* aFilebuf, nsIFile* aDir,
+ParseMFUnpacked(const char* aFilebuf, nsIFile* aDir, SECOidTag aDigestAlgorithm,
                 /*out*/ nsTHashtable<nsStringHashKey>& aMfItems,
                 ScopedAutoSECItem& aBuf)
 {
-  nsresult rv;
+  const char* digestNameToFind = nullptr;
+  switch (aDigestAlgorithm) {
+    case SEC_OID_SHA256:
+      digestNameToFind = "sha256-digest";
+      break;
+    case SEC_OID_SHA1:
+      digestNameToFind = "sha1-digest";
+      break;
+    default:
+      MOZ_ASSERT_UNREACHABLE("bad argument to ParseMF");
+      return NS_ERROR_FAILURE;
+  }
 
   const char* nextLineStart = aFilebuf;
-
-  rv = CheckManifestVersion(nextLineStart, NS_LITERAL_CSTRING(JAR_MF_HEADER));
+  nsresult 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 {
@@ -1092,17 +1173,19 @@ ParseMFUnpacked(const char* aFilebuf, ns
 
       if (aMfItems.Contains(curItemName)) {
         // Duplicate entry
         return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
       }
 
       // Verify that the file's content digest matches the digest from this
       // MF section.
-      rv = VerifyFileContentDigest(aDir, curItemName, digest, aBuf);
+      DigestWithAlgorithm digestWithAlgorithm = { digest, aDigestAlgorithm };
+      rv = VerifyFileContentDigest(aDir, curItemName, digestWithAlgorithm,
+                                   aBuf);
       if (NS_FAILED(rv)) {
         return rv;
       }
 
       aMfItems.PutEntry(curItemName);
 
       if (*nextLineStart == '\0') {
         // end-of-file
@@ -1122,19 +1205,19 @@ ParseMFUnpacked(const char* aFilebuf, ns
     rv = ParseAttribute(curLine, attrName, attrValue);
     if (NS_FAILED(rv)) {
       return rv;
     }
 
     // Lines to look for:
 
     // (1) Digest:
-    if (attrName.LowerCaseEqualsLiteral("sha1-digest")) {
+    if (attrName.EqualsIgnoreCase(digestNameToFind)) {
       if (!digest.IsEmpty()) {
-        // multiple SHA1 digests in section
+        // multiple SHA* digests in section
         return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
       }
 
       rv = Base64Decode(attrValue, digest);
       if (NS_FAILED(rv)) {
         return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
       }
 
@@ -1293,76 +1376,78 @@ VerifySignedDirectory(AppTrustedRoot aTr
 
   nsAutoString sigFilename;
   rv = FindSignatureFilename(metaDir, sigFilename);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   ScopedAutoSECItem sigBuffer;
-  rv = LoadOneMetafile(metaDir, sigFilename, sigBuffer, nullptr);
+  rv = LoadOneMetafile(metaDir, sigFilename, sigBuffer);
   if (NS_FAILED(rv)) {
     return NS_ERROR_SIGNED_JAR_NOT_SIGNED;
   }
 
   // Load the signature (SF) file and verify the signature.
   // The .sf and .rsa files must have the same name apart from the extension.
 
   nsAutoString sfFilename(Substring(sigFilename, 0, sigFilename.Length() - 3)
                           + NS_LITERAL_STRING("sf"));
 
   ScopedAutoSECItem sfBuffer;
   Digest sfCalculatedDigest;
-  rv = LoadOneMetafile(metaDir, sfFilename, sfBuffer, &sfCalculatedDigest);
+  rv = LoadOneMetafile(metaDir, sfFilename, sfBuffer, SEC_OID_SHA1,
+                       &sfCalculatedDigest);
   if (NS_FAILED(rv)) {
     return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
   }
 
   sigBuffer.type = siBuffer;
   UniqueCERTCertList builtChain;
   rv = VerifySignature(aTrustedRoot, sigBuffer, sfCalculatedDigest.get(),
                        builtChain);
   if (NS_FAILED(rv)) {
     return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
   }
 
   // Get the expected manifest hash from the signed .sf file
 
-  nsAutoCString mfDigest;
+  DigestWithAlgorithm mfDigest;
   rv = ParseSF(BitwiseCast<char*, unsigned char*>(sfBuffer.data), mfDigest);
   if (NS_FAILED(rv)) {
     return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
   }
 
   // Load manifest (MF) file and verify signature
 
   nsAutoString mfFilename(NS_LITERAL_STRING("manifest.mf"));
   ScopedAutoSECItem manifestBuffer;
   Digest mfCalculatedDigest;
-  rv = LoadOneMetafile(metaDir, mfFilename, manifestBuffer, &mfCalculatedDigest);
+  rv = LoadOneMetafile(metaDir, mfFilename, manifestBuffer, mfDigest.mAlgorithm,
+                       &mfCalculatedDigest);
   if (NS_FAILED(rv)) {
     return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
   }
 
   nsDependentCSubstring calculatedDigest(
     DigestToDependentString(mfCalculatedDigest));
-  if (!mfDigest.Equals(calculatedDigest)) {
+  if (!mfDigest.mDigest.Equals(calculatedDigest)) {
     return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
   }
 
   // Parse manifest and verify signed hash of all listed files
 
   // 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<nsStringHashKey> items;
   rv = ParseMFUnpacked(BitwiseCast<char*, unsigned char*>(manifestBuffer.data),
-                       aDirectory, items, buf);
+                       aDirectory, mfDigest.mAlgorithm, items, buf);
   if (NS_FAILED(rv)){
     return rv;
   }
 
   // We've checked that everything listed in the manifest exists and is signed
   // correctly. Now check on disk for extra (unsigned) files.
   // Deletes found entries from items as it goes.
   rv = CheckDirForUnsignedFiles(aDirectory, EmptyString(), items,
--- a/security/manager/ssl/tests/unit/sign_app.py
+++ b/security/manager/ssl/tests/unit/sign_app.py
@@ -5,17 +5,17 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 """
 Given a directory of files, packages them up and signs the
 resulting zip file. Mainly for creating test inputs to the
 nsIX509CertDB.openSignedAppFileAsync API.
 """
 from base64 import b64encode
-from hashlib import sha1
+from hashlib import sha1, sha256
 import StringIO
 import argparse
 import os
 import pycms
 import re
 import zipfile
 
 def walkDirectory(directory):
@@ -27,59 +27,103 @@ def walkDirectory(directory):
     paths = []
     for path, dirs, files in os.walk(directory):
         for f in files:
             fullPath = os.path.join(path, f)
             internalPath = re.sub(r'^/', '', fullPath.replace(directory, ''))
             paths.append((fullPath, internalPath))
     return paths
 
-def signZip(appDirectory, outputFile, issuerName, doSign):
+def signZip(appDirectory, outputFile, issuerName, manifestHashes,
+            signatureHashes, doSign):
     """Given a directory containing the files to package up,
     an output filename to write to, the name of the issuer of
-    the signing certificate, and whether or not to actually
-    sign the resulting package, packages up the files in the
-    directory and creates the output as appropriate."""
+    the signing certificate, a list of hash algorithms to use in
+    the manifest file, a similar list for the signature file,
+    and whether or not to actually sign the resulting package,
+    packages up the files in the directory and creates the
+    output as appropriate."""
     mfEntries = []
 
     with zipfile.ZipFile(outputFile, 'w') as outZip:
         for (fullPath, internalPath) in walkDirectory(appDirectory):
             with open(fullPath) as inputFile:
                 contents = inputFile.read()
             outZip.writestr(internalPath, contents)
 
             # Add the entry to the manifest we're building
-            base64hash = b64encode(sha1(contents).digest())
-            mfEntries.append(
-                'Name: %s\nSHA1-Digest: %s\n' % (internalPath, base64hash))
+            mfEntry = 'Name: %s\n' % internalPath
+            for (hashFunc, name) in manifestHashes:
+                base64hash = b64encode(hashFunc(contents).digest())
+                mfEntry += '%s-Digest: %s\n' % (name, base64hash)
+            mfEntries.append(mfEntry)
 
         # Just exit early if we're not actually signing.
         if not doSign:
             return
 
         mfContents = 'Manifest-Version: 1.0\n\n' + '\n'.join(mfEntries)
-        base64hash = b64encode(sha1(mfContents).digest())
-        sfContents = 'Signature-Version: 1.0\nSHA1-Digest-Manifest: %s\n' % base64hash
+        sfContents = 'Signature-Version: 1.0\n'
+        for (hashFunc, name) in signatureHashes:
+            base64hash = b64encode(hashFunc(mfContents).digest())
+            sfContents += '%s-Digest-Manifest: %s\n' % (name, base64hash)
+
         cmsSpecification = 'hash:%s\nsigner:\n' % sha1(sfContents).hexdigest() + \
             'issuer:%s\n' % issuerName + \
             'subject:xpcshell signed app test signer\n' + \
             'extension:keyUsage:digitalSignature'
         cmsSpecificationStream = StringIO.StringIO()
         print >>cmsSpecificationStream, cmsSpecification
         cmsSpecificationStream.seek(0)
         cms = pycms.CMS(cmsSpecificationStream)
         p7 = cms.toDER()
         outZip.writestr('META-INF/A.RSA', p7)
         outZip.writestr('META-INF/A.SF', sfContents)
         outZip.writestr('META-INF/MANIFEST.MF', mfContents)
 
+class Error(Exception):
+    """Base class for exceptions in this module."""
+    pass
+
+
+class UnknownHashAlgorithmError(Error):
+    """Helper exception type to handle unknown hash algorithms."""
+
+    def __init__(self, name):
+        super(UnknownHashAlgorithmError, self).__init__()
+        self.name = name
+
+    def __str__(self):
+        return 'Unknown hash algorithm %s' % repr(self.name)
+
+
+def hashNameToFunctionAndIdentifier(name):
+    if name == 'sha1':
+        return (sha1, 'SHA1')
+    if name == 'sha256':
+        return (sha256, 'SHA256')
+    raise UnknownHashAlgorithmError(name)
+
 def main(outputFile, appPath, *args):
     """Main entrypoint. Given an already-opened file-like
     object, a path to the app directory to sign, and some
     optional arguments, signs the contents of the directory and
     writes the resulting package to the 'file'."""
     parser = argparse.ArgumentParser(description='Sign an app.')
     parser.add_argument('-n', '--no-sign', action='store_true',
                         help='Don\'t actually sign - only create zip')
     parser.add_argument('-i', '--issuer', action='store', help='Issuer name',
                         default='xpcshell signed apps test root')
+    parser.add_argument('-m', '--manifest-hash', action='append',
+                        help='Hash algorithms to use in manifest',
+                        default=[])
+    parser.add_argument('-s', '--signature-hash', action='append',
+                        help='Hash algorithms to use in signature file',
+                        default=[])
     parsed = parser.parse_args(args)
-    signZip(appPath, outputFile, parsed.issuer, not parsed.no_sign)
+    if len(parsed.manifest_hash) == 0:
+        parsed.manifest_hash.append('sha256')
+    if len(parsed.signature_hash) == 0:
+        parsed.signature_hash.append('sha256')
+    signZip(appPath, outputFile, parsed.issuer,
+            map(hashNameToFunctionAndIdentifier, parsed.manifest_hash),
+            map(hashNameToFunctionAndIdentifier, parsed.signature_hash),
+            not parsed.no_sign)
--- a/security/manager/ssl/tests/unit/test_signed_apps.js
+++ b/security/manager/ssl/tests/unit/test_signed_apps.js
@@ -137,16 +137,77 @@ function tampered_app_path(test_name) {
 add_test(function () {
   certdb.openSignedAppFileAsync(
     Ci.nsIX509CertDB.AppXPCShellRoot, original_app_path("signed_app"),
     check_open_result("valid", Cr.NS_OK));
 });
 
 add_test(function () {
   certdb.openSignedAppFileAsync(
+    Ci.nsIX509CertDB.AppXPCShellRoot, original_app_path("signed_app_sha256"),
+    check_open_result("valid with sha256 hashes in manifest", Cr.NS_OK));
+});
+
+add_test(function () {
+  certdb.openSignedAppFileAsync(
+    Ci.nsIX509CertDB.AppXPCShellRoot,
+    original_app_path("signed_app_sha1_and_sha256"),
+    check_open_result("valid with sha1 and sha256 hashes in manifest", Cr.NS_OK));
+});
+
+add_test(function () {
+  certdb.openSignedAppFileAsync(
+    Ci.nsIX509CertDB.AppXPCShellRoot,
+    original_app_path("signed_app_sha256_manifest"),
+    check_open_result("sha256 hashes in manifest, but only a sha1 hash in the signature file",
+                      Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID));
+});
+
+add_test(function () {
+  certdb.openSignedAppFileAsync(
+    Ci.nsIX509CertDB.AppXPCShellRoot,
+    original_app_path("signed_app_sha256_signature_file"),
+    check_open_result("sha256 hash in the signature file, but only sha1 hashes in the manifest",
+                      Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID));
+});
+
+add_test(function () {
+  certdb.openSignedAppFileAsync(
+    Ci.nsIX509CertDB.AppXPCShellRoot,
+    original_app_path("sha1_and_sha256_manifest_sha1_signature_file"),
+    check_open_result("sha1 and sha256 hashes in the manifest, but only sha1 hash in the signature file",
+                      Cr.NS_OK));
+});
+
+add_test(function () {
+  certdb.openSignedAppFileAsync(
+    Ci.nsIX509CertDB.AppXPCShellRoot,
+    original_app_path("sha1_and_sha256_manifest_sha256_signature_file"),
+    check_open_result("sha1 and sha256 hashes in the manifest, but only sha256 hash in the signature file",
+                      Cr.NS_OK));
+});
+
+add_test(function () {
+  certdb.openSignedAppFileAsync(
+    Ci.nsIX509CertDB.AppXPCShellRoot,
+    original_app_path("sha1_manifest_sha1_and_sha256_signature_file"),
+    check_open_result("only sha1 in the manifest, sha1 and sha256 hashes in the signature file",
+                      Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID));
+});
+
+add_test(function () {
+  certdb.openSignedAppFileAsync(
+    Ci.nsIX509CertDB.AppXPCShellRoot,
+    original_app_path("sha256_manifest_sha1_and_sha256_signature_file"),
+    check_open_result("only sha256 in the manifest, sha1 and sha256 hashes in the signature file",
+                      Cr.NS_OK));
+});
+
+add_test(function () {
+  certdb.openSignedAppFileAsync(
     Ci.nsIX509CertDB.AppXPCShellRoot, original_app_path("unsigned_app"),
     check_open_result("unsigned", Cr.NS_ERROR_SIGNED_JAR_NOT_SIGNED));
 });
 
 add_test(function () {
   certdb.openSignedAppFileAsync(
     Ci.nsIX509CertDB.AppXPCShellRoot, original_app_path("unknown_issuer_app"),
     check_open_result("unknown_issuer",
--- a/security/manager/ssl/tests/unit/test_signed_apps/moz.build
+++ b/security/manager/ssl/tests/unit/test_signed_apps/moz.build
@@ -20,15 +20,23 @@ def SignedAppFile(name, flags):
     # TEST_HARNESS_FILES.xpcshell.security.manager.ssl.tests.unit.test_signed_apps.
     files = TEST_HARNESS_FILES.xpcshell
     for part in RELATIVEDIR.split('/'):
         files = files[part]
     files += ['!%s' % name]
 
 # Temporarily disabled. See bug 1256495.
 #signed_app_files = (
-#    ['signed_app.zip'],
-#    ['unknown_issuer_app.zip', '-i', 'unknown issuer'],
+#    ['signed_app.zip', '-m', 'sha1', '-s', 'sha1'],
+#    ['unknown_issuer_app.zip', '-i', 'unknown issuer', '-m', 'sha1', '-s', 'sha1'],
 #    ['unsigned_app.zip', '-n'],
+#    ['signed_app_sha256.zip'],
+#    ['signed_app_sha1_and_sha256.zip', '-m', 'sha256', '-m', 'sha1', '-s', 'sha256', '-s', 'sha1'],
+#    ['signed_app_sha256_manifest.zip', '-s', 'sha1'],
+#    ['signed_app_sha256_signature_file.zip', '-m', 'sha1'],
+#    ['sha1_and_sha256_manifest_sha1_signature_file.zip', '-m', 'sha256', '-m', 'sha1', '-s', 'sha1'],
+#    ['sha1_and_sha256_manifest_sha256_signature_file.zip', '-m', 'sha256', '-m', 'sha1'],
+#    ['sha1_manifest_sha1_and_sha256_signature_file.zip', '-m', 'sha1', '-s', 'sha1', '-s', 'sha256'],
+#    ['sha256_manifest_sha1_and_sha256_signature_file.zip', '-s', 'sha1', '-s', 'sha256'],
 #)
 #
 #for signed_app_file_params in signed_app_files:
 #    SignedAppFile(signed_app_file_params[0], signed_app_file_params[1:])
new file mode 100644
index 0000000000000000000000000000000000000000..5770318d0dd764eb641fa32fbc3f32b6bdeea107
GIT binary patch
literal 2993
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
ZZQ;P4$OF7t!QsIMgegE>=AZ^E0|070PX+)0
new file mode 100644
index 0000000000000000000000000000000000000000..2c6e371c1fcb06361d7ca682039736151eb44fd7
GIT binary patch
literal 3011
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
new file mode 100644
index 0000000000000000000000000000000000000000..4a3c13debcbb5e21f760c7f692f0d754a92acde1
GIT binary patch
literal 2882
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
new file mode 100644
index 0000000000000000000000000000000000000000..d556c06c84987aaf6cab1cdb2e766488a6cf3c87
GIT binary patch
literal 2936
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
index 022181dc73b95307ea3d5597b1933fdd52606f34..6fe760615ac4f5816a4cb9772f717b166e4f2eae
GIT binary patch
literal 2813
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
new file mode 100644
index 0000000000000000000000000000000000000000..947616ebbeac123e45db4dfd617a184f37dcab81
GIT binary patch
literal 3062
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
S%?b_=HXuv^>Z$>?dKm!Rcw>10
new file mode 100644
index 0000000000000000000000000000000000000000..c2ad381b16ebc6838cc9534abf20b891ff4c146d
GIT binary patch
literal 2885
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
new file mode 100644
index 0000000000000000000000000000000000000000..e88095e6eeed65f94c50d63d6e941c15ade58c1a
GIT binary patch
literal 2867
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
new file mode 100644
index 0000000000000000000000000000000000000000..945930f81c5aeaccaea7e87062286dadd3aa58e6
GIT binary patch
literal 2831
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
K49BUQARYiaX%z$j
index 177f545d478d99507224c8e2e31690b68a9d652c..cccaa9993e2c10a513da6b6bed3918ec939a44e6
GIT binary patch
literal 2781
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
index 748386dc560aac505a5f7d490d556f5a5c295ebf..f1cbb1d751edf9b2b4696c843a1f8df2f1a4b482
GIT binary patch
literal 971
zc$^FHW@Zs#00IBvaBry?jZJ<)HVE?qac*K>W?E`-iC$K5eqJ?~f`SrEB)%-Qs5mn}
zPsvKbNCza5mzbLh<S2yzB^6vNN>cN{(qK7+S|tNL13g0}u3D}D6pO!k80u&P*&xgY
z#6hl(F21fI8JWcjKva^Es!)`gn39{Skd|Kr=9Yl0NrhRXhi2UDCQEiPCI$u&<^zhQ
zB$g!VXXYlRr|K2trFRDSx$|;Kae+MP>ERLtq^E%}2gqg{#==Jo42*X@T^vIy=Da<(
zk(0qd;K+vC^)Js0F*X-2jJy4A(*F+*b4JmSMLlu}1xi)>WQ6yC(u=37pUXO@gr)#*
zMkWzv+<5^88W<VSa|XIr^wf;dS_x0v=z7qTEJDu-j07Cu%?dV_83<PcX)b0E4*=M_
Bfu#Td