LCOV - code coverage report
Current view: top level - security/apps - AppSignatureVerification.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 0 596 0.0 %
Date: 2017-07-14 16:53:18 Functions: 0 33 0.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
       2             : /* vim: set ts=2 et sw=2 tw=80: */
       3             : /* This Source Code Form is subject to the terms of the Mozilla Public
       4             :  * License, v. 2.0. If a copy of the MPL was not distributed with this
       5             :  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
       6             : 
       7             : #include "nsNSSCertificateDB.h"
       8             : 
       9             : #include "AppTrustDomain.h"
      10             : #include "CryptoTask.h"
      11             : #include "NSSCertDBTrustDomain.h"
      12             : #include "ScopedNSSTypes.h"
      13             : #include "certdb.h"
      14             : #include "mozilla/Base64.h"
      15             : #include "mozilla/Casting.h"
      16             : #include "mozilla/Logging.h"
      17             : #include "mozilla/RefPtr.h"
      18             : #include "mozilla/UniquePtr.h"
      19             : #include "nsCOMPtr.h"
      20             : #include "nsComponentManagerUtils.h"
      21             : #include "nsDataSignatureVerifier.h"
      22             : #include "nsDependentString.h"
      23             : #include "nsHashKeys.h"
      24             : #include "nsIDirectoryEnumerator.h"
      25             : #include "nsIFile.h"
      26             : #include "nsIFileStreams.h"
      27             : #include "nsIInputStream.h"
      28             : #include "nsIStringEnumerator.h"
      29             : #include "nsIZipReader.h"
      30             : #include "nsNSSCertificate.h"
      31             : #include "nsNetUtil.h"
      32             : #include "nsProxyRelease.h"
      33             : #include "nsString.h"
      34             : #include "nsTHashtable.h"
      35             : #include "pkix/pkix.h"
      36             : #include "pkix/pkixnss.h"
      37             : #include "plstr.h"
      38             : #include "secmime.h"
      39             : 
      40             : 
      41             : using namespace mozilla::pkix;
      42             : using namespace mozilla;
      43             : using namespace mozilla::psm;
      44             : 
      45             : extern mozilla::LazyLogModule gPIPNSSLog;
      46             : 
      47             : namespace {
      48             : 
      49             : // The digest must have a lifetime greater than or equal to the returned string.
      50             : inline nsDependentCSubstring
      51           0 : DigestToDependentString(const Digest& digest)
      52             : {
      53             :   return nsDependentCSubstring(
      54           0 :     BitwiseCast<char*, unsigned char*>(digest.get().data),
      55           0 :     digest.get().len);
      56             : }
      57             : 
      58             : // Reads a maximum of 1MB from a stream into the supplied buffer.
      59             : // The reason for the 1MB limit is because this function is used to read
      60             : // signature-related files and we want to avoid OOM. The uncompressed length of
      61             : // an entry can be hundreds of times larger than the compressed version,
      62             : // especially if someone has specifically crafted the entry to cause OOM or to
      63             : // consume massive amounts of disk space.
      64             : //
      65             : // @param stream  The input stream to read from.
      66             : // @param buf     The buffer that we read the stream into, which must have
      67             : //                already been allocated.
      68             : nsresult
      69           0 : ReadStream(const nsCOMPtr<nsIInputStream>& stream, /*out*/ SECItem& buf)
      70             : {
      71             :   // The size returned by Available() might be inaccurate so we need
      72             :   // to check that Available() matches up with the actual length of
      73             :   // the file.
      74             :   uint64_t length;
      75           0 :   nsresult rv = stream->Available(&length);
      76           0 :   if (NS_WARN_IF(NS_FAILED(rv))) {
      77           0 :     return rv;
      78             :   }
      79             : 
      80             :   // Cap the maximum accepted size of signature-related files at 1MB (which is
      81             :   // still crazily huge) to avoid OOM. The uncompressed length of an entry can be
      82             :   // hundreds of times larger than the compressed version, especially if
      83             :   // someone has speifically crafted the entry to cause OOM or to consume
      84             :   // massive amounts of disk space.
      85             :   static const uint32_t MAX_LENGTH = 1024 * 1024;
      86           0 :   if (length > MAX_LENGTH) {
      87           0 :     return NS_ERROR_FILE_TOO_BIG;
      88             :   }
      89             : 
      90             :   // With bug 164695 in mind we +1 to leave room for null-terminating
      91             :   // the buffer.
      92           0 :   SECITEM_AllocItem(buf, static_cast<uint32_t>(length + 1));
      93             : 
      94             :   // buf.len == length + 1. We attempt to read length + 1 bytes
      95             :   // instead of length, so that we can check whether the metadata for
      96             :   // the entry is incorrect.
      97             :   uint32_t bytesRead;
      98           0 :   rv = stream->Read(BitwiseCast<char*, unsigned char*>(buf.data), buf.len,
      99           0 :                     &bytesRead);
     100           0 :   if (NS_WARN_IF(NS_FAILED(rv))) {
     101           0 :     return rv;
     102             :   }
     103           0 :   if (bytesRead != length) {
     104           0 :     return NS_ERROR_FILE_CORRUPTED;
     105             :   }
     106             : 
     107           0 :   buf.data[buf.len - 1] = 0; // null-terminate
     108             : 
     109           0 :   return NS_OK;
     110             : }
     111             : 
     112             : // Finds exactly one (signature metadata) JAR entry that matches the given
     113             : // search pattern, and then load it. Fails if there are no matches or if
     114             : // there is more than one match. If bugDigest is not null then on success
     115             : // bufDigest will contain the SHA-1 digeset of the entry.
     116             : nsresult
     117           0 : FindAndLoadOneEntry(nsIZipReader * zip,
     118             :                     const nsACString & searchPattern,
     119             :                     /*out*/ nsACString & filename,
     120             :                     /*out*/ SECItem & buf,
     121             :                     /*optional, out*/ Digest * bufDigest)
     122             : {
     123           0 :   nsCOMPtr<nsIUTF8StringEnumerator> files;
     124           0 :   nsresult rv = zip->FindEntries(searchPattern, getter_AddRefs(files));
     125           0 :   if (NS_FAILED(rv) || !files) {
     126           0 :     return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
     127             :   }
     128             : 
     129             :   bool more;
     130           0 :   rv = files->HasMore(&more);
     131           0 :   NS_ENSURE_SUCCESS(rv, rv);
     132           0 :   if (!more) {
     133           0 :     return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
     134             :   }
     135             : 
     136           0 :   rv = files->GetNext(filename);
     137           0 :   NS_ENSURE_SUCCESS(rv, rv);
     138             : 
     139             :   // Check if there is more than one match, if so then error!
     140           0 :   rv = files->HasMore(&more);
     141           0 :   NS_ENSURE_SUCCESS(rv, rv);
     142           0 :   if (more) {
     143           0 :     return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
     144             :   }
     145             : 
     146           0 :   nsCOMPtr<nsIInputStream> stream;
     147           0 :   rv = zip->GetInputStream(filename, getter_AddRefs(stream));
     148           0 :   NS_ENSURE_SUCCESS(rv, rv);
     149             : 
     150           0 :   rv = ReadStream(stream, buf);
     151           0 :   if (NS_WARN_IF(NS_FAILED(rv))) {
     152           0 :     return NS_ERROR_SIGNED_JAR_ENTRY_INVALID;
     153             :   }
     154             : 
     155           0 :   if (bufDigest) {
     156           0 :     rv = bufDigest->DigestBuf(SEC_OID_SHA1, buf.data, buf.len - 1);
     157           0 :     NS_ENSURE_SUCCESS(rv, rv);
     158             :   }
     159             : 
     160           0 :   return NS_OK;
     161             : }
     162             : 
     163             : // Verify the digest of an entry. We avoid loading the entire entry into memory
     164             : // at once, which would require memory in proportion to the size of the largest
     165             : // entry. Instead, we require only a small, fixed amount of memory.
     166             : //
     167             : // @param stream  an input stream from a JAR entry or file depending on whether
     168             : //                it is from a signed archive or unpacked into a directory
     169             : // @param digestFromManifest The digest that we're supposed to check the file's
     170             : //                           contents against, from the manifest
     171             : // @param buf A scratch buffer that we use for doing the I/O, which must have
     172             : //            already been allocated. The size of this buffer is the unit
     173             : //            size of our I/O.
     174             : nsresult
     175           0 : VerifyStreamContentDigest(nsIInputStream* stream,
     176             :                           const nsCString& digestFromManifest, SECItem& buf)
     177             : {
     178           0 :   MOZ_ASSERT(buf.len > 0);
     179           0 :   if (digestFromManifest.Length() != SHA1_LENGTH) {
     180           0 :     return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
     181             :   }
     182             : 
     183             :   nsresult rv;
     184             :   uint64_t len64;
     185           0 :   rv = stream->Available(&len64);
     186           0 :   NS_ENSURE_SUCCESS(rv, rv);
     187           0 :   if (len64 > UINT32_MAX) {
     188           0 :     return NS_ERROR_SIGNED_JAR_ENTRY_TOO_LARGE;
     189             :   }
     190             : 
     191           0 :   UniquePK11Context digestContext(PK11_CreateDigestContext(SEC_OID_SHA1));
     192           0 :   if (!digestContext) {
     193           0 :     return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
     194             :   }
     195             : 
     196           0 :   rv = MapSECStatus(PK11_DigestBegin(digestContext.get()));
     197           0 :   NS_ENSURE_SUCCESS(rv, rv);
     198             : 
     199           0 :   uint64_t totalBytesRead = 0;
     200             :   for (;;) {
     201             :     uint32_t bytesRead;
     202           0 :     rv = stream->Read(BitwiseCast<char*, unsigned char*>(buf.data), buf.len,
     203           0 :                       &bytesRead);
     204           0 :     NS_ENSURE_SUCCESS(rv, rv);
     205             : 
     206           0 :     if (bytesRead == 0) {
     207           0 :       break; // EOF
     208             :     }
     209             : 
     210           0 :     totalBytesRead += bytesRead;
     211           0 :     if (totalBytesRead >= UINT32_MAX) {
     212           0 :       return NS_ERROR_SIGNED_JAR_ENTRY_TOO_LARGE;
     213             :     }
     214             : 
     215           0 :     rv = MapSECStatus(PK11_DigestOp(digestContext.get(), buf.data, bytesRead));
     216           0 :     NS_ENSURE_SUCCESS(rv, rv);
     217           0 :   }
     218             : 
     219           0 :   if (totalBytesRead != len64) {
     220             :     // The metadata we used for Available() doesn't match the actual size of
     221             :     // the entry.
     222           0 :     return NS_ERROR_SIGNED_JAR_ENTRY_INVALID;
     223             :   }
     224             : 
     225             :   // Verify that the digests match.
     226           0 :   Digest digest;
     227           0 :   rv = digest.End(SEC_OID_SHA1, digestContext);
     228           0 :   NS_ENSURE_SUCCESS(rv, rv);
     229             : 
     230           0 :   nsDependentCSubstring digestStr(DigestToDependentString(digest));
     231           0 :   if (!digestStr.Equals(digestFromManifest)) {
     232           0 :     return NS_ERROR_SIGNED_JAR_MODIFIED_ENTRY;
     233             :   }
     234             : 
     235           0 :   return NS_OK;
     236             : }
     237             : 
     238             : nsresult
     239           0 : VerifyEntryContentDigest(nsIZipReader* zip, const nsACString& aFilename,
     240             :                          const nsCString& digestFromManifest, SECItem& buf)
     241             : {
     242           0 :   nsCOMPtr<nsIInputStream> stream;
     243           0 :   nsresult rv = zip->GetInputStream(aFilename, getter_AddRefs(stream));
     244           0 :   if (NS_FAILED(rv)) {
     245           0 :     return NS_ERROR_SIGNED_JAR_ENTRY_MISSING;
     246             :   }
     247             : 
     248           0 :   return VerifyStreamContentDigest(stream, digestFromManifest, buf);
     249             : }
     250             : 
     251             : // @oaram aDir       directory containing the unpacked signed archive
     252             : // @param aFilename  path of the target file relative to aDir
     253             : // @param digestFromManifest The digest that we're supposed to check the file's
     254             : //                           contents against, from the manifest
     255             : // @param buf A scratch buffer that we use for doing the I/O
     256             : nsresult
     257           0 : VerifyFileContentDigest(nsIFile* aDir, const nsAString& aFilename,
     258             :                         const nsCString& digestFromManifest, SECItem& buf)
     259             : {
     260             :   // Find the file corresponding to the manifest path
     261           0 :   nsCOMPtr<nsIFile> file;
     262           0 :   nsresult rv = aDir->Clone(getter_AddRefs(file));
     263           0 :   if (NS_FAILED(rv)) {
     264           0 :     return rv;
     265             :   }
     266             : 
     267             :   // We don't know how to handle JARs with signed directory entries.
     268             :   // It's technically possible in the manifest but makes no sense on disk.
     269             :   // Inside an archive we just ignore them, but here we have to treat it
     270             :   // as an error because the signed bytes never got unpacked.
     271           0 :   int32_t pos = 0;
     272             :   int32_t slash;
     273           0 :   int32_t namelen = aFilename.Length();
     274           0 :   if (namelen == 0 || aFilename[namelen - 1] == '/') {
     275           0 :     return NS_ERROR_SIGNED_JAR_ENTRY_INVALID;
     276             :   }
     277             : 
     278             :   // Append path segments one by one
     279           0 :   do {
     280           0 :     slash = aFilename.FindChar('/', pos);
     281           0 :     int32_t segend = (slash == kNotFound) ? namelen : slash;
     282           0 :     rv = file->Append(Substring(aFilename, pos, (segend - pos)));
     283           0 :     if (NS_FAILED(rv)) {
     284           0 :       return NS_ERROR_SIGNED_JAR_ENTRY_INVALID;
     285             :     }
     286           0 :     pos = slash + 1;
     287           0 :   }  while (pos < namelen && slash != kNotFound);
     288             : 
     289             :   bool exists;
     290           0 :   rv = file->Exists(&exists);
     291           0 :   if (NS_FAILED(rv) || !exists) {
     292           0 :     return NS_ERROR_SIGNED_JAR_ENTRY_MISSING;
     293             :   }
     294             : 
     295             :   bool isDir;
     296           0 :   rv = file->IsDirectory(&isDir);
     297           0 :   if (NS_FAILED(rv) || isDir) {
     298             :     // We only support signed files, not directory entries
     299           0 :     return NS_ERROR_SIGNED_JAR_ENTRY_INVALID;
     300             :   }
     301             : 
     302             :   // Open an input stream for that file and verify it.
     303           0 :   nsCOMPtr<nsIInputStream> stream;
     304           0 :   rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), file, -1, -1,
     305           0 :                                   nsIFileInputStream::CLOSE_ON_EOF);
     306           0 :   if (NS_FAILED(rv) || !stream) {
     307           0 :     return NS_ERROR_SIGNED_JAR_ENTRY_MISSING;
     308             :   }
     309             : 
     310           0 :   return VerifyStreamContentDigest(stream, digestFromManifest, buf);
     311             : }
     312             : 
     313             : // On input, nextLineStart is the start of the current line. On output,
     314             : // nextLineStart is the start of the next line.
     315             : nsresult
     316           0 : ReadLine(/*in/out*/ const char* & nextLineStart, /*out*/ nsCString & line,
     317             :          bool allowContinuations = true)
     318             : {
     319           0 :   line.Truncate();
     320           0 :   size_t previousLength = 0;
     321           0 :   size_t currentLength = 0;
     322             :   for (;;) {
     323           0 :     const char* eol = PL_strpbrk(nextLineStart, "\r\n");
     324             : 
     325           0 :     if (!eol) { // Reached end of file before newline
     326           0 :       eol = nextLineStart + strlen(nextLineStart);
     327             :     }
     328             : 
     329           0 :     previousLength = currentLength;
     330           0 :     line.Append(nextLineStart, eol - nextLineStart);
     331           0 :     currentLength = line.Length();
     332             : 
     333             :     // The spec says "No line may be longer than 72 bytes (not characters)"
     334             :     // in its UTF8-encoded form.
     335             :     static const size_t lineLimit = 72;
     336           0 :     if (currentLength - previousLength > lineLimit) {
     337           0 :       return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
     338             :     }
     339             : 
     340             :     // The spec says: "Implementations should support 65535-byte
     341             :     // (not character) header values..."
     342           0 :     if (currentLength > 65535) {
     343           0 :       return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
     344             :     }
     345             : 
     346           0 :     if (*eol == '\r') {
     347           0 :       ++eol;
     348             :     }
     349           0 :     if (*eol == '\n') {
     350           0 :       ++eol;
     351             :     }
     352             : 
     353           0 :     nextLineStart = eol;
     354             : 
     355           0 :     if (*eol != ' ') {
     356             :       // not a continuation
     357           0 :       return NS_OK;
     358             :     }
     359             : 
     360             :     // continuation
     361           0 :     if (!allowContinuations) {
     362           0 :       return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
     363             :     }
     364             : 
     365           0 :     ++nextLineStart; // skip space and keep appending
     366           0 :   }
     367             : }
     368             : 
     369             : // The header strings are defined in the JAR specification.
     370             : #define JAR_MF_SEARCH_STRING "(M|/M)ETA-INF/(M|m)(ANIFEST|anifest).(MF|mf)$"
     371             : #define JAR_SF_SEARCH_STRING "(M|/M)ETA-INF/*.(SF|sf)$"
     372             : #define JAR_RSA_SEARCH_STRING "(M|/M)ETA-INF/*.(RSA|rsa)$"
     373             : #define JAR_META_DIR "META-INF"
     374             : #define JAR_MF_HEADER "Manifest-Version: 1.0"
     375             : #define JAR_SF_HEADER "Signature-Version: 1.0"
     376             : 
     377             : nsresult
     378           0 : ParseAttribute(const nsAutoCString & curLine,
     379             :                /*out*/ nsAutoCString & attrName,
     380             :                /*out*/ nsAutoCString & attrValue)
     381             : {
     382             :   // Find the colon that separates the name from the value.
     383           0 :   int32_t colonPos = curLine.FindChar(':');
     384           0 :   if (colonPos == kNotFound) {
     385           0 :     return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
     386             :   }
     387             : 
     388             :   // set attrName to the name, skipping spaces between the name and colon
     389           0 :   int32_t nameEnd = colonPos;
     390             :   for (;;) {
     391           0 :     if (nameEnd == 0) {
     392           0 :       return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; // colon with no name
     393             :     }
     394           0 :     if (curLine[nameEnd - 1] != ' ')
     395           0 :       break;
     396           0 :     --nameEnd;
     397             :   }
     398           0 :   curLine.Left(attrName, nameEnd);
     399             : 
     400             :   // Set attrValue to the value, skipping spaces between the colon and the
     401             :   // value. The value may be empty.
     402           0 :   int32_t valueStart = colonPos + 1;
     403           0 :   int32_t curLineLength = curLine.Length();
     404           0 :   while (valueStart != curLineLength && curLine[valueStart] == ' ') {
     405           0 :     ++valueStart;
     406             :   }
     407           0 :   curLine.Right(attrValue, curLineLength - valueStart);
     408             : 
     409           0 :   return NS_OK;
     410             : }
     411             : 
     412             : // Parses the version line of the MF or SF header.
     413             : nsresult
     414           0 : CheckManifestVersion(const char* & nextLineStart,
     415             :                      const nsACString & expectedHeader)
     416             : {
     417             :   // The JAR spec says: "Manifest-Version and Signature-Version must be first,
     418             :   // and in exactly that case (so that they can be recognized easily as magic
     419             :   // strings)."
     420           0 :   nsAutoCString curLine;
     421           0 :   nsresult rv = ReadLine(nextLineStart, curLine, false);
     422           0 :   if (NS_FAILED(rv)) {
     423           0 :     return rv;
     424             :   }
     425           0 :   if (!curLine.Equals(expectedHeader)) {
     426           0 :     return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
     427             :   }
     428           0 :   return NS_OK;
     429             : }
     430             : 
     431             : // Parses a signature file (SF) as defined in the JDK 8 JAR Specification.
     432             : //
     433             : // The SF file *must* contain exactly one SHA1-Digest-Manifest attribute in
     434             : // the main section. All other sections are ignored. This means that this will
     435             : // NOT parse old-style signature files that have separate digests per entry.
     436             : // The JDK8 x-Digest-Manifest variant is better because:
     437             : //
     438             : //   (1) It allows us to follow the principle that we should minimize the
     439             : //       processing of data that we do before we verify its signature. In
     440             : //       particular, with the x-Digest-Manifest style, we can verify the digest
     441             : //       of MANIFEST.MF before we parse it, which prevents malicious JARs
     442             : //       exploiting our MANIFEST.MF parser.
     443             : //   (2) It is more time-efficient and space-efficient to have one
     444             : //       x-Digest-Manifest instead of multiple x-Digest values.
     445             : //
     446             : // In order to get benefit (1), we do NOT implement the fallback to the older
     447             : // mechanism as the spec requires/suggests. Also, for simplity's sake, we only
     448             : // support exactly one SHA1-Digest-Manifest attribute, and no other
     449             : // algorithms.
     450             : //
     451             : // filebuf must be null-terminated. On output, mfDigest will contain the
     452             : // decoded value of SHA1-Digest-Manifest.
     453             : nsresult
     454           0 : ParseSF(const char* filebuf, /*out*/ nsCString& mfDigest)
     455             : {
     456           0 :   const char* nextLineStart = filebuf;
     457           0 :   nsresult rv = CheckManifestVersion(nextLineStart,
     458           0 :                                      NS_LITERAL_CSTRING(JAR_SF_HEADER));
     459           0 :   if (NS_FAILED(rv)) {
     460           0 :     return rv;
     461             :   }
     462             : 
     463             :   // Find SHA1-Digest-Manifest
     464             :   for (;;) {
     465           0 :     nsAutoCString curLine;
     466           0 :     rv = ReadLine(nextLineStart, curLine);
     467           0 :     if (NS_FAILED(rv)) {
     468           0 :       return rv;
     469             :     }
     470             : 
     471           0 :     if (curLine.Length() == 0) {
     472             :       // End of main section (blank line or end-of-file), and no
     473             :       // SHA1-Digest-Manifest found.
     474           0 :       return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
     475             :     }
     476             : 
     477           0 :     nsAutoCString attrName;
     478           0 :     nsAutoCString attrValue;
     479           0 :     rv = ParseAttribute(curLine, attrName, attrValue);
     480           0 :     if (NS_FAILED(rv)) {
     481           0 :       return rv;
     482             :     }
     483             : 
     484           0 :     if (attrName.LowerCaseEqualsLiteral("sha1-digest-manifest")) {
     485           0 :       rv = Base64Decode(attrValue, mfDigest);
     486           0 :       if (NS_FAILED(rv)) {
     487           0 :         return rv;
     488             :       }
     489             : 
     490             :       // There could be multiple SHA1-Digest-Manifest attributes, which
     491             :       // would be an error, but it's better to just skip any erroneous
     492             :       // duplicate entries rather than trying to detect them, because:
     493             :       //
     494             :       //   (1) It's simpler, and simpler generally means more secure
     495             :       //   (2) An attacker can't make us accept a JAR we would otherwise
     496             :       //       reject just by adding additional SHA1-Digest-Manifest
     497             :       //       attributes.
     498           0 :       break;
     499             :     }
     500             : 
     501             :     // ignore unrecognized attributes
     502           0 :   }
     503             : 
     504           0 :   return NS_OK;
     505             : }
     506             : 
     507             : // Parses MANIFEST.MF. The filenames of all entries will be returned in
     508             : // mfItems. buf must be a pre-allocated scratch buffer that is used for doing
     509             : // I/O.
     510             : nsresult
     511           0 : ParseMF(const char* filebuf, nsIZipReader * zip,
     512             :         /*out*/ nsTHashtable<nsCStringHashKey> & mfItems,
     513             :         ScopedAutoSECItem & buf)
     514             : {
     515             :   nsresult rv;
     516             : 
     517           0 :   const char* nextLineStart = filebuf;
     518             : 
     519           0 :   rv = CheckManifestVersion(nextLineStart, NS_LITERAL_CSTRING(JAR_MF_HEADER));
     520           0 :   if (NS_FAILED(rv)) {
     521           0 :     return rv;
     522             :   }
     523             : 
     524             :   // Skip the rest of the header section, which ends with a blank line.
     525             :   {
     526           0 :     nsAutoCString line;
     527           0 :     do {
     528           0 :       rv = ReadLine(nextLineStart, line);
     529           0 :       if (NS_FAILED(rv)) {
     530           0 :         return rv;
     531             :       }
     532           0 :     } while (line.Length() > 0);
     533             : 
     534             :     // Manifest containing no file entries is OK, though useless.
     535           0 :     if (*nextLineStart == '\0') {
     536           0 :       return NS_OK;
     537             :     }
     538             :   }
     539             : 
     540           0 :   nsAutoCString curItemName;
     541           0 :   nsAutoCString digest;
     542             : 
     543             :   for (;;) {
     544           0 :     nsAutoCString curLine;
     545           0 :     rv = ReadLine(nextLineStart, curLine);
     546           0 :     NS_ENSURE_SUCCESS(rv, rv);
     547             : 
     548           0 :     if (curLine.Length() == 0) {
     549             :       // end of section (blank line or end-of-file)
     550             : 
     551           0 :       if (curItemName.Length() == 0) {
     552             :         // '...Each section must start with an attribute with the name as
     553             :         // "Name",...', so every section must have a Name attribute.
     554           0 :         return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
     555             :       }
     556             : 
     557           0 :       if (digest.IsEmpty()) {
     558             :         // We require every entry to have a digest, since we require every
     559             :         // entry to be signed and we don't allow duplicate entries.
     560           0 :         return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
     561             :       }
     562             : 
     563           0 :       if (mfItems.Contains(curItemName)) {
     564             :         // Duplicate entry
     565           0 :         return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
     566             :       }
     567             : 
     568             :       // Verify that the entry's content digest matches the digest from this
     569             :       // MF section.
     570           0 :       rv = VerifyEntryContentDigest(zip, curItemName, digest, buf);
     571           0 :       if (NS_FAILED(rv))
     572           0 :         return rv;
     573             : 
     574           0 :       mfItems.PutEntry(curItemName);
     575             : 
     576           0 :       if (*nextLineStart == '\0') // end-of-file
     577           0 :         break;
     578             : 
     579             :       // reset so we know we haven't encountered either of these for the next
     580             :       // item yet.
     581           0 :       curItemName.Truncate();
     582           0 :       digest.Truncate();
     583             : 
     584           0 :       continue; // skip the rest of the loop below
     585             :     }
     586             : 
     587           0 :     nsAutoCString attrName;
     588           0 :     nsAutoCString attrValue;
     589           0 :     rv = ParseAttribute(curLine, attrName, attrValue);
     590           0 :     if (NS_FAILED(rv)) {
     591           0 :       return rv;
     592             :     }
     593             : 
     594             :     // Lines to look for:
     595             : 
     596             :     // (1) Digest:
     597           0 :     if (attrName.LowerCaseEqualsLiteral("sha1-digest"))
     598             :     {
     599           0 :       if (!digest.IsEmpty()) { // multiple SHA1 digests in section
     600           0 :         return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
     601             :       }
     602             : 
     603           0 :       rv = Base64Decode(attrValue, digest);
     604           0 :       if (NS_FAILED(rv)) {
     605           0 :         return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
     606             :       }
     607             : 
     608           0 :       continue;
     609             :     }
     610             : 
     611             :     // (2) Name: associates this manifest section with a file in the jar.
     612           0 :     if (attrName.LowerCaseEqualsLiteral("name"))
     613             :     {
     614           0 :       if (MOZ_UNLIKELY(curItemName.Length() > 0)) // multiple names in section
     615           0 :         return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
     616             : 
     617           0 :       if (MOZ_UNLIKELY(attrValue.Length() == 0))
     618           0 :         return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
     619             : 
     620           0 :       curItemName = attrValue;
     621             : 
     622           0 :       continue;
     623             :     }
     624             : 
     625             :     // (3) Magic: the only other must-understand attribute
     626           0 :     if (attrName.LowerCaseEqualsLiteral("magic")) {
     627             :       // We don't understand any magic, so we can't verify an entry that
     628             :       // requires magic. Since we require every entry to have a valid
     629             :       // signature, we have no choice but to reject the entry.
     630           0 :       return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
     631             :     }
     632             : 
     633             :     // unrecognized attributes must be ignored
     634           0 :   }
     635             : 
     636           0 :   return NS_OK;
     637             : }
     638             : 
     639             : struct VerifyCertificateContext {
     640             :   AppTrustedRoot trustedRoot;
     641             :   UniqueCERTCertList& builtChain;
     642             : };
     643             : 
     644             : nsresult
     645           0 : VerifyCertificate(CERTCertificate* signerCert, void* voidContext, void* pinArg)
     646             : {
     647             :   // TODO: null pinArg is tolerated.
     648           0 :   if (NS_WARN_IF(!signerCert) || NS_WARN_IF(!voidContext)) {
     649           0 :     return NS_ERROR_INVALID_ARG;
     650             :   }
     651             :   const VerifyCertificateContext& context =
     652           0 :     *static_cast<const VerifyCertificateContext*>(voidContext);
     653             : 
     654           0 :   AppTrustDomain trustDomain(context.builtChain, pinArg);
     655           0 :   nsresult rv = trustDomain.SetTrustedRoot(context.trustedRoot);
     656           0 :   if (NS_FAILED(rv)) {
     657           0 :     return rv;
     658             :   }
     659           0 :   Input certDER;
     660           0 :   mozilla::pkix::Result result = certDER.Init(signerCert->derCert.data,
     661           0 :                                               signerCert->derCert.len);
     662           0 :   if (result != Success) {
     663           0 :     return mozilla::psm::GetXPCOMFromNSSError(MapResultToPRErrorCode(result));
     664             :   }
     665             : 
     666           0 :   result = BuildCertChain(trustDomain, certDER, Now(),
     667             :                           EndEntityOrCA::MustBeEndEntity,
     668             :                           KeyUsage::digitalSignature,
     669             :                           KeyPurposeId::id_kp_codeSigning,
     670             :                           CertPolicyId::anyPolicy,
     671           0 :                           nullptr /*stapledOCSPResponse*/);
     672           0 :   if (result == mozilla::pkix::Result::ERROR_EXPIRED_CERTIFICATE) {
     673             :     // For code-signing you normally need trusted 3rd-party timestamps to
     674             :     // handle expiration properly. The signer could always mess with their
     675             :     // system clock so you can't trust the certificate was un-expired when
     676             :     // the signing took place. The choice is either to ignore expiration
     677             :     // or to enforce expiration at time of use. The latter leads to the
     678             :     // user-hostile result that perfectly good code stops working.
     679             :     //
     680             :     // Our package format doesn't support timestamps (nor do we have a
     681             :     // trusted 3rd party timestamper), but since we sign all of our apps and
     682             :     // add-ons ourselves we can trust ourselves not to mess with the clock
     683             :     // on the signing systems. We also have a revocation mechanism if we
     684             :     // need it. It's OK to ignore cert expiration under these conditions.
     685             :     //
     686             :     // This is an invalid approach if
     687             :     //  * we issue certs to let others sign their own packages
     688             :     //  * mozilla::pkix returns "expired" when there are "worse" problems
     689             :     //    with the certificate or chain.
     690             :     // (see bug 1267318)
     691           0 :     result = Success;
     692             :   }
     693           0 :   if (result != Success) {
     694           0 :     return mozilla::psm::GetXPCOMFromNSSError(MapResultToPRErrorCode(result));
     695             :   }
     696             : 
     697           0 :   return NS_OK;
     698             : }
     699             : 
     700             : nsresult
     701           0 : VerifySignature(AppTrustedRoot trustedRoot, const SECItem& buffer,
     702             :                 const SECItem& detachedDigest,
     703             :                 /*out*/ UniqueCERTCertList& builtChain)
     704             : {
     705             :   // Currently, this function is only called within the CalculateResult() method
     706             :   // of CryptoTasks. As such, NSS should not be shut down at this point and the
     707             :   // CryptoTask implementation should already hold a nsNSSShutDownPreventionLock.
     708             :   // We acquire a nsNSSShutDownPreventionLock here solely to prove we did to
     709             :   // VerifyCMSDetachedSignatureIncludingCertificate().
     710           0 :   nsNSSShutDownPreventionLock locker;
     711           0 :   VerifyCertificateContext context = { trustedRoot, builtChain };
     712             :   // XXX: missing pinArg
     713             :   return VerifyCMSDetachedSignatureIncludingCertificate(buffer, detachedDigest,
     714             :                                                         VerifyCertificate,
     715             :                                                         &context, nullptr,
     716           0 :                                                         locker);
     717             : }
     718             : 
     719             : NS_IMETHODIMP
     720           0 : OpenSignedAppFile(AppTrustedRoot aTrustedRoot, nsIFile* aJarFile,
     721             :                   /*out, optional */ nsIZipReader** aZipReader,
     722             :                   /*out, optional */ nsIX509Cert** aSignerCert)
     723             : {
     724           0 :   NS_ENSURE_ARG_POINTER(aJarFile);
     725             : 
     726           0 :   if (aZipReader) {
     727           0 :     *aZipReader = nullptr;
     728             :   }
     729             : 
     730           0 :   if (aSignerCert) {
     731           0 :     *aSignerCert = nullptr;
     732             :   }
     733             : 
     734             :   nsresult rv;
     735             : 
     736             :   static NS_DEFINE_CID(kZipReaderCID, NS_ZIPREADER_CID);
     737           0 :   nsCOMPtr<nsIZipReader> zip = do_CreateInstance(kZipReaderCID, &rv);
     738           0 :   NS_ENSURE_SUCCESS(rv, rv);
     739             : 
     740           0 :   rv = zip->Open(aJarFile);
     741           0 :   NS_ENSURE_SUCCESS(rv, rv);
     742             : 
     743             :   // Signature (RSA) file
     744           0 :   nsAutoCString sigFilename;
     745           0 :   ScopedAutoSECItem sigBuffer;
     746           0 :   rv = FindAndLoadOneEntry(zip, nsLiteralCString(JAR_RSA_SEARCH_STRING),
     747           0 :                            sigFilename, sigBuffer, nullptr);
     748           0 :   if (NS_FAILED(rv)) {
     749           0 :     return NS_ERROR_SIGNED_JAR_NOT_SIGNED;
     750             :   }
     751             : 
     752             :   // Signature (SF) file
     753           0 :   nsAutoCString sfFilename;
     754           0 :   ScopedAutoSECItem sfBuffer;
     755           0 :   Digest sfCalculatedDigest;
     756           0 :   rv = FindAndLoadOneEntry(zip, NS_LITERAL_CSTRING(JAR_SF_SEARCH_STRING),
     757           0 :                            sfFilename, sfBuffer, &sfCalculatedDigest);
     758           0 :   if (NS_FAILED(rv)) {
     759           0 :     return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
     760             :   }
     761             : 
     762           0 :   sigBuffer.type = siBuffer;
     763           0 :   UniqueCERTCertList builtChain;
     764           0 :   rv = VerifySignature(aTrustedRoot, sigBuffer, sfCalculatedDigest.get(),
     765             :                        builtChain);
     766           0 :   if (NS_FAILED(rv)) {
     767           0 :     return rv;
     768             :   }
     769             : 
     770           0 :   nsAutoCString mfDigest;
     771           0 :   rv = ParseSF(BitwiseCast<char*, unsigned char*>(sfBuffer.data), mfDigest);
     772           0 :   if (NS_FAILED(rv)) {
     773           0 :     return rv;
     774             :   }
     775             : 
     776             :   // Manifest (MF) file
     777           0 :   nsAutoCString mfFilename;
     778           0 :   ScopedAutoSECItem manifestBuffer;
     779           0 :   Digest mfCalculatedDigest;
     780           0 :   rv = FindAndLoadOneEntry(zip, NS_LITERAL_CSTRING(JAR_MF_SEARCH_STRING),
     781           0 :                            mfFilename, manifestBuffer, &mfCalculatedDigest);
     782           0 :   if (NS_FAILED(rv)) {
     783           0 :     return rv;
     784             :   }
     785             : 
     786             :   nsDependentCSubstring calculatedDigest(
     787           0 :     DigestToDependentString(mfCalculatedDigest));
     788           0 :   if (!mfDigest.Equals(calculatedDigest)) {
     789           0 :     return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
     790             :   }
     791             : 
     792             :   // Allocate the I/O buffer only once per JAR, instead of once per entry, in
     793             :   // order to minimize malloc/free calls and in order to avoid fragmenting
     794             :   // memory.
     795           0 :   ScopedAutoSECItem buf(128 * 1024);
     796             : 
     797           0 :   nsTHashtable<nsCStringHashKey> items;
     798             : 
     799           0 :   rv = ParseMF(BitwiseCast<char*, unsigned char*>(manifestBuffer.data), zip,
     800             :                items, buf);
     801           0 :   if (NS_FAILED(rv)) {
     802           0 :     return rv;
     803             :   }
     804             : 
     805             :   // Verify every entry in the file.
     806           0 :   nsCOMPtr<nsIUTF8StringEnumerator> entries;
     807           0 :   rv = zip->FindEntries(EmptyCString(), getter_AddRefs(entries));
     808           0 :   if (NS_SUCCEEDED(rv) && !entries) {
     809           0 :     rv = NS_ERROR_UNEXPECTED;
     810             :   }
     811           0 :   if (NS_FAILED(rv)) {
     812           0 :     return rv;
     813             :   }
     814             : 
     815             :   for (;;) {
     816             :     bool hasMore;
     817           0 :     rv = entries->HasMore(&hasMore);
     818           0 :     NS_ENSURE_SUCCESS(rv, rv);
     819             : 
     820           0 :     if (!hasMore) {
     821           0 :       break;
     822             :     }
     823             : 
     824           0 :     nsAutoCString entryFilename;
     825           0 :     rv = entries->GetNext(entryFilename);
     826           0 :     NS_ENSURE_SUCCESS(rv, rv);
     827             : 
     828           0 :     MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("Verifying digests for %s",
     829             :            entryFilename.get()));
     830             : 
     831             :     // The files that comprise the signature mechanism are not covered by the
     832             :     // signature.
     833             :     //
     834             :     // XXX: This is OK for a single signature, but doesn't work for
     835             :     // multiple signatures, because the metadata for the other signatures
     836             :     // is not signed either.
     837           0 :     if (entryFilename == mfFilename ||
     838           0 :         entryFilename == sfFilename ||
     839           0 :         entryFilename == sigFilename) {
     840           0 :       continue;
     841             :     }
     842             : 
     843           0 :     if (entryFilename.Length() == 0) {
     844           0 :       return NS_ERROR_SIGNED_JAR_ENTRY_INVALID;
     845             :     }
     846             : 
     847             :     // Entries with names that end in "/" are directory entries, which are not
     848             :     // signed.
     849             :     //
     850             :     // XXX: As long as we don't unpack the JAR into the filesystem, the "/"
     851             :     // entries are harmless. But, it is not clear what the security
     852             :     // implications of directory entries are if/when we were to unpackage the
     853             :     // JAR into the filesystem.
     854           0 :     if (entryFilename[entryFilename.Length() - 1] == '/') {
     855           0 :       continue;
     856             :     }
     857             : 
     858           0 :     nsCStringHashKey * item = items.GetEntry(entryFilename);
     859           0 :     if (!item) {
     860           0 :       return NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY;
     861             :     }
     862             : 
     863             :     // Remove the item so we can check for leftover items later
     864           0 :     items.RemoveEntry(item);
     865           0 :   }
     866             : 
     867             :   // We verified that every entry that we require to be signed is signed. But,
     868             :   // were there any missing entries--that is, entries that are mentioned in the
     869             :   // manifest but missing from the archive?
     870           0 :   if (items.Count() != 0) {
     871           0 :     return NS_ERROR_SIGNED_JAR_ENTRY_MISSING;
     872             :   }
     873             : 
     874             :   // Return the reader to the caller if they want it
     875           0 :   if (aZipReader) {
     876           0 :     zip.forget(aZipReader);
     877             :   }
     878             : 
     879             :   // Return the signer's certificate to the reader if they want it.
     880             :   // XXX: We should return an nsIX509CertList with the whole validated chain.
     881           0 :   if (aSignerCert) {
     882           0 :     CERTCertListNode* signerCertNode = CERT_LIST_HEAD(builtChain);
     883           0 :     if (!signerCertNode || CERT_LIST_END(signerCertNode, builtChain) ||
     884           0 :         !signerCertNode->cert) {
     885           0 :       return NS_ERROR_FAILURE;
     886             :     }
     887             :     nsCOMPtr<nsIX509Cert> signerCert =
     888           0 :       nsNSSCertificate::Create(signerCertNode->cert);
     889           0 :     NS_ENSURE_TRUE(signerCert, NS_ERROR_OUT_OF_MEMORY);
     890           0 :     signerCert.forget(aSignerCert);
     891             :   }
     892             : 
     893           0 :   return NS_OK;
     894             : }
     895             : 
     896           0 : class OpenSignedAppFileTask final : public CryptoTask
     897             : {
     898             : public:
     899           0 :   OpenSignedAppFileTask(AppTrustedRoot aTrustedRoot, nsIFile* aJarFile,
     900             :                         nsIOpenSignedAppFileCallback* aCallback)
     901           0 :     : mTrustedRoot(aTrustedRoot)
     902             :     , mJarFile(aJarFile)
     903             :     , mCallback(new nsMainThreadPtrHolder<nsIOpenSignedAppFileCallback>(
     904           0 :         "OpenSignedAppFileTask::mCallback", aCallback))
     905             :   {
     906           0 :   }
     907             : 
     908             : private:
     909           0 :   virtual nsresult CalculateResult() override
     910             :   {
     911           0 :     return OpenSignedAppFile(mTrustedRoot, mJarFile,
     912           0 :                              getter_AddRefs(mZipReader),
     913           0 :                              getter_AddRefs(mSignerCert));
     914             :   }
     915             : 
     916             :   // nsNSSCertificate implements nsNSSShutdownObject, so there's nothing that
     917             :   // needs to be released
     918           0 :   virtual void ReleaseNSSResources() override { }
     919             : 
     920           0 :   virtual void CallCallback(nsresult rv) override
     921             :   {
     922           0 :     (void) mCallback->OpenSignedAppFileFinished(rv, mZipReader, mSignerCert);
     923           0 :   }
     924             : 
     925             :   const AppTrustedRoot mTrustedRoot;
     926             :   const nsCOMPtr<nsIFile> mJarFile;
     927             :   nsMainThreadPtrHandle<nsIOpenSignedAppFileCallback> mCallback;
     928             :   nsCOMPtr<nsIZipReader> mZipReader; // out
     929             :   nsCOMPtr<nsIX509Cert> mSignerCert; // out
     930             : };
     931             : 
     932             : } // unnamed namespace
     933             : 
     934             : NS_IMETHODIMP
     935           0 : nsNSSCertificateDB::OpenSignedAppFileAsync(
     936             :   AppTrustedRoot aTrustedRoot, nsIFile* aJarFile,
     937             :   nsIOpenSignedAppFileCallback* aCallback)
     938             : {
     939           0 :   NS_ENSURE_ARG_POINTER(aJarFile);
     940           0 :   NS_ENSURE_ARG_POINTER(aCallback);
     941             :   RefPtr<OpenSignedAppFileTask> task(new OpenSignedAppFileTask(aTrustedRoot,
     942             :                                                                aJarFile,
     943           0 :                                                                aCallback));
     944           0 :   return task->Dispatch("SignedJAR");
     945             : }
     946             : 
     947             : //
     948             : // Signature verification for archives unpacked into a file structure
     949             : //
     950             : 
     951             : // Finds the "*.rsa" signature file in the META-INF directory and returns
     952             : // the name. It is an error if there are none or more than one .rsa file
     953             : nsresult
     954           0 : FindSignatureFilename(nsIFile* aMetaDir,
     955             :                       /*out*/ nsAString& aFilename)
     956             : {
     957           0 :   nsCOMPtr<nsISimpleEnumerator> entries;
     958           0 :   nsresult rv = aMetaDir->GetDirectoryEntries(getter_AddRefs(entries));
     959           0 :   nsCOMPtr<nsIDirectoryEnumerator> files = do_QueryInterface(entries);
     960           0 :   if (NS_FAILED(rv) || !files) {
     961           0 :     return NS_ERROR_SIGNED_JAR_NOT_SIGNED;
     962             :   }
     963             : 
     964           0 :   bool found = false;
     965           0 :   nsCOMPtr<nsIFile> file;
     966           0 :   rv = files->GetNextFile(getter_AddRefs(file));
     967             : 
     968           0 :   while (NS_SUCCEEDED(rv) && file) {
     969           0 :     nsAutoString leafname;
     970           0 :     rv = file->GetLeafName(leafname);
     971           0 :     if (NS_SUCCEEDED(rv)) {
     972           0 :       if (StringEndsWith(leafname, NS_LITERAL_STRING(".rsa"))) {
     973           0 :         if (!found) {
     974           0 :           found = true;
     975           0 :           aFilename = leafname;
     976             :         } else {
     977             :           // second signature file is an error
     978           0 :           rv = NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
     979           0 :           break;
     980             :         }
     981             :       }
     982           0 :       rv = files->GetNextFile(getter_AddRefs(file));
     983             :     }
     984             :   }
     985             : 
     986           0 :   if (!found) {
     987           0 :     rv = NS_ERROR_SIGNED_JAR_NOT_SIGNED;
     988             :   }
     989             : 
     990           0 :   files->Close();
     991           0 :   return rv;
     992             : }
     993             : 
     994             : // Loads the signature metadata file that matches the given filename in
     995             : // the passed-in Meta-inf directory. If bufDigest is not null then on
     996             : // success bufDigest will contain the SHA-1 digest of the entry.
     997             : nsresult
     998           0 : LoadOneMetafile(nsIFile* aMetaDir,
     999             :                 const nsAString& aFilename,
    1000             :                 /*out*/ SECItem& aBuf,
    1001             :                 /*optional, out*/ Digest* aBufDigest)
    1002             : {
    1003           0 :   nsCOMPtr<nsIFile> metafile;
    1004           0 :   nsresult rv = aMetaDir->Clone(getter_AddRefs(metafile));
    1005           0 :   NS_ENSURE_SUCCESS(rv, rv);
    1006             : 
    1007           0 :   rv = metafile->Append(aFilename);
    1008           0 :   NS_ENSURE_SUCCESS(rv, rv);
    1009             : 
    1010             :   bool exists;
    1011           0 :   rv = metafile->Exists(&exists);
    1012           0 :   if (NS_FAILED(rv) || !exists) {
    1013             :     // we can call a missing .rsa file "unsigned" but FindSignatureFilename()
    1014             :     // already found one: missing other metadata files means a broken signature.
    1015           0 :     return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
    1016             :   }
    1017             : 
    1018           0 :   nsCOMPtr<nsIInputStream> stream;
    1019           0 :   rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), metafile);
    1020           0 :   NS_ENSURE_SUCCESS(rv, rv);
    1021             : 
    1022           0 :   rv = ReadStream(stream, aBuf);
    1023           0 :   stream->Close();
    1024           0 :   NS_ENSURE_SUCCESS(rv, rv);
    1025             : 
    1026           0 :   if (aBufDigest) {
    1027           0 :     rv = aBufDigest->DigestBuf(SEC_OID_SHA1, aBuf.data, aBuf.len - 1);
    1028           0 :     NS_ENSURE_SUCCESS(rv, rv);
    1029             :   }
    1030             : 
    1031           0 :   return NS_OK;
    1032             : }
    1033             : 
    1034             : // Parses MANIFEST.MF and verifies the contents of the unpacked files
    1035             : // listed in the manifest.
    1036             : // The filenames of all entries will be returned in aMfItems. aBuf must
    1037             : // be a pre-allocated scratch buffer that is used for doing I/O.
    1038             : nsresult
    1039           0 : ParseMFUnpacked(const char* aFilebuf, nsIFile* aDir,
    1040             :                 /*out*/ nsTHashtable<nsStringHashKey>& aMfItems,
    1041             :                 ScopedAutoSECItem& aBuf)
    1042             : {
    1043             :   nsresult rv;
    1044             : 
    1045           0 :   const char* nextLineStart = aFilebuf;
    1046             : 
    1047           0 :   rv = CheckManifestVersion(nextLineStart, NS_LITERAL_CSTRING(JAR_MF_HEADER));
    1048           0 :   if (NS_FAILED(rv)) {
    1049           0 :     return rv;
    1050             :   }
    1051             : 
    1052             :   // Skip the rest of the header section, which ends with a blank line.
    1053             :   {
    1054           0 :     nsAutoCString line;
    1055           0 :     do {
    1056           0 :       rv = ReadLine(nextLineStart, line);
    1057           0 :       if (NS_FAILED(rv)) {
    1058           0 :         return rv;
    1059             :       }
    1060           0 :     } while (line.Length() > 0);
    1061             : 
    1062             :     // Manifest containing no file entries is OK, though useless.
    1063           0 :     if (*nextLineStart == '\0') {
    1064           0 :       return NS_OK;
    1065             :     }
    1066             :   }
    1067             : 
    1068           0 :   nsAutoString curItemName;
    1069           0 :   nsAutoCString digest;
    1070             : 
    1071             :   for (;;) {
    1072           0 :     nsAutoCString curLine;
    1073           0 :     rv = ReadLine(nextLineStart, curLine);
    1074           0 :     if (NS_FAILED(rv)) {
    1075           0 :       return rv;
    1076             :     }
    1077             : 
    1078           0 :     if (curLine.Length() == 0) {
    1079             :       // end of section (blank line or end-of-file)
    1080             : 
    1081           0 :       if (curItemName.Length() == 0) {
    1082             :         // '...Each section must start with an attribute with the name as
    1083             :         // "Name",...', so every section must have a Name attribute.
    1084           0 :         return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
    1085             :       }
    1086             : 
    1087           0 :       if (digest.IsEmpty()) {
    1088             :         // We require every entry to have a digest, since we require every
    1089             :         // entry to be signed and we don't allow duplicate entries.
    1090           0 :         return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
    1091             :       }
    1092             : 
    1093           0 :       if (aMfItems.Contains(curItemName)) {
    1094             :         // Duplicate entry
    1095           0 :         return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
    1096             :       }
    1097             : 
    1098             :       // Verify that the file's content digest matches the digest from this
    1099             :       // MF section.
    1100           0 :       rv = VerifyFileContentDigest(aDir, curItemName, digest, aBuf);
    1101           0 :       if (NS_FAILED(rv)) {
    1102           0 :         return rv;
    1103             :       }
    1104             : 
    1105           0 :       aMfItems.PutEntry(curItemName);
    1106             : 
    1107           0 :       if (*nextLineStart == '\0') {
    1108             :         // end-of-file
    1109           0 :         break;
    1110             :       }
    1111             : 
    1112             :       // reset so we know we haven't encountered either of these for the next
    1113             :       // item yet.
    1114           0 :       curItemName.Truncate();
    1115           0 :       digest.Truncate();
    1116             : 
    1117           0 :       continue; // skip the rest of the loop below
    1118             :     }
    1119             : 
    1120           0 :     nsAutoCString attrName;
    1121           0 :     nsAutoCString attrValue;
    1122           0 :     rv = ParseAttribute(curLine, attrName, attrValue);
    1123           0 :     if (NS_FAILED(rv)) {
    1124           0 :       return rv;
    1125             :     }
    1126             : 
    1127             :     // Lines to look for:
    1128             : 
    1129             :     // (1) Digest:
    1130           0 :     if (attrName.LowerCaseEqualsLiteral("sha1-digest")) {
    1131           0 :       if (!digest.IsEmpty()) {
    1132             :         // multiple SHA1 digests in section
    1133           0 :         return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
    1134             :       }
    1135             : 
    1136           0 :       rv = Base64Decode(attrValue, digest);
    1137           0 :       if (NS_FAILED(rv)) {
    1138           0 :         return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
    1139             :       }
    1140             : 
    1141           0 :       continue;
    1142             :     }
    1143             : 
    1144             :     // (2) Name: associates this manifest section with a file in the jar.
    1145           0 :     if (attrName.LowerCaseEqualsLiteral("name")) {
    1146           0 :       if (MOZ_UNLIKELY(curItemName.Length() > 0)) {
    1147             :         // multiple names in section
    1148           0 :         return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
    1149             :       }
    1150             : 
    1151           0 :       if (MOZ_UNLIKELY(attrValue.Length() == 0)) {
    1152           0 :         return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
    1153             :       }
    1154             : 
    1155           0 :       curItemName = NS_ConvertUTF8toUTF16(attrValue);
    1156             : 
    1157           0 :       continue;
    1158             :     }
    1159             : 
    1160             :     // (3) Magic: the only other must-understand attribute
    1161           0 :     if (attrName.LowerCaseEqualsLiteral("magic")) {
    1162             :       // We don't understand any magic, so we can't verify an entry that
    1163             :       // requires magic. Since we require every entry to have a valid
    1164             :       // signature, we have no choice but to reject the entry.
    1165           0 :       return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
    1166             :     }
    1167             : 
    1168             :     // unrecognized attributes must be ignored
    1169           0 :   }
    1170             : 
    1171           0 :   return NS_OK;
    1172             : }
    1173             : 
    1174             : // recursively check a directory tree for files not in the list of
    1175             : // verified files we found in the manifest. For each file we find
    1176             : // Check it against the files found in the manifest. If the file wasn't
    1177             : // in the manifest then it's unsigned and we can stop looking. Otherwise
    1178             : // remove it from the collection so we can check leftovers later.
    1179             : //
    1180             : // @param aDir   Directory to check
    1181             : // @param aPath  Relative path to that directory (to check against aItems)
    1182             : // @param aItems All the files found
    1183             : // @param *Filename  signature files that won't be in the manifest
    1184             : nsresult
    1185           0 : CheckDirForUnsignedFiles(nsIFile* aDir,
    1186             :                          const nsString& aPath,
    1187             :                          /* in/out */ nsTHashtable<nsStringHashKey>& aItems,
    1188             :                          const nsAString& sigFilename,
    1189             :                          const nsAString& sfFilename,
    1190             :                          const nsAString& mfFilename)
    1191             : {
    1192           0 :   nsCOMPtr<nsISimpleEnumerator> entries;
    1193           0 :   nsresult rv = aDir->GetDirectoryEntries(getter_AddRefs(entries));
    1194           0 :   nsCOMPtr<nsIDirectoryEnumerator> files = do_QueryInterface(entries);
    1195           0 :   if (NS_FAILED(rv) || !files) {
    1196           0 :     return NS_ERROR_SIGNED_JAR_ENTRY_MISSING;
    1197             :   }
    1198             : 
    1199           0 :   bool inMeta = StringBeginsWith(aPath, NS_LITERAL_STRING(JAR_META_DIR));
    1200             : 
    1201           0 :   while (NS_SUCCEEDED(rv)) {
    1202           0 :     nsCOMPtr<nsIFile> file;
    1203           0 :     rv = files->GetNextFile(getter_AddRefs(file));
    1204           0 :     if (NS_FAILED(rv) || !file) {
    1205           0 :       break;
    1206             :     }
    1207             : 
    1208           0 :     nsAutoString leafname;
    1209           0 :     rv = file->GetLeafName(leafname);
    1210           0 :     if (NS_FAILED(rv)) {
    1211           0 :       return rv;
    1212             :     }
    1213             : 
    1214           0 :     nsAutoString curName(aPath + leafname);
    1215             : 
    1216             :     bool isDir;
    1217           0 :     rv = file->IsDirectory(&isDir);
    1218           0 :     if (NS_FAILED(rv)) {
    1219           0 :       return rv;
    1220             :     }
    1221             : 
    1222             :     // if it's a directory we need to recurse
    1223           0 :     if (isDir) {
    1224           0 :       curName.Append(NS_LITERAL_STRING("/"));
    1225           0 :       rv = CheckDirForUnsignedFiles(file, curName, aItems,
    1226           0 :                                     sigFilename, sfFilename, mfFilename);
    1227             :     } else {
    1228             :       // The files that comprise the signature mechanism are not covered by the
    1229             :       // signature.
    1230             :       //
    1231             :       // XXX: This is OK for a single signature, but doesn't work for
    1232             :       // multiple signatures because the metadata for the other signatures
    1233             :       // is not signed either.
    1234           0 :       if (inMeta && ( leafname == sigFilename ||
    1235           0 :                       leafname == sfFilename ||
    1236           0 :                       leafname == mfFilename )) {
    1237           0 :         continue;
    1238             :       }
    1239             : 
    1240             :       // make sure the current file was found in the manifest
    1241           0 :       nsStringHashKey* item = aItems.GetEntry(curName);
    1242           0 :       if (!item) {
    1243           0 :         return NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY;
    1244             :       }
    1245             : 
    1246             :       // Remove the item so we can check for leftover items later
    1247           0 :       aItems.RemoveEntry(item);
    1248             :     }
    1249             :   }
    1250           0 :   files->Close();
    1251           0 :   return rv;
    1252             : }
    1253             : 
    1254             : /*
    1255             :  * Verify the signature of a directory structure as if it were a
    1256             :  * signed JAR file (used for unpacked JARs)
    1257             :  */
    1258             : nsresult
    1259           0 : VerifySignedDirectory(AppTrustedRoot aTrustedRoot,
    1260             :                       nsIFile* aDirectory,
    1261             :                       /*out, optional */ nsIX509Cert** aSignerCert)
    1262             : {
    1263           0 :   NS_ENSURE_ARG_POINTER(aDirectory);
    1264             : 
    1265           0 :   if (aSignerCert) {
    1266           0 :     *aSignerCert = nullptr;
    1267             :   }
    1268             : 
    1269             :   // Make sure there's a META-INF directory
    1270             : 
    1271           0 :   nsCOMPtr<nsIFile> metaDir;
    1272           0 :   nsresult rv = aDirectory->Clone(getter_AddRefs(metaDir));
    1273           0 :   if (NS_FAILED(rv)) {
    1274           0 :     return rv;
    1275             :   }
    1276           0 :   rv = metaDir->Append(NS_LITERAL_STRING(JAR_META_DIR));
    1277           0 :   if (NS_FAILED(rv)) {
    1278           0 :     return rv;
    1279             :   }
    1280             : 
    1281             :   bool exists;
    1282           0 :   rv = metaDir->Exists(&exists);
    1283           0 :   if (NS_FAILED(rv) || !exists) {
    1284           0 :     return NS_ERROR_SIGNED_JAR_NOT_SIGNED;
    1285             :   }
    1286             :   bool isDirectory;
    1287           0 :   rv = metaDir->IsDirectory(&isDirectory);
    1288           0 :   if (NS_FAILED(rv) || !isDirectory) {
    1289           0 :     return NS_ERROR_SIGNED_JAR_NOT_SIGNED;
    1290             :   }
    1291             : 
    1292             :   // Find and load the Signature (RSA) file
    1293             : 
    1294           0 :   nsAutoString sigFilename;
    1295           0 :   rv = FindSignatureFilename(metaDir, sigFilename);
    1296           0 :   if (NS_FAILED(rv)) {
    1297           0 :     return rv;
    1298             :   }
    1299             : 
    1300           0 :   ScopedAutoSECItem sigBuffer;
    1301           0 :   rv = LoadOneMetafile(metaDir, sigFilename, sigBuffer, nullptr);
    1302           0 :   if (NS_FAILED(rv)) {
    1303           0 :     return NS_ERROR_SIGNED_JAR_NOT_SIGNED;
    1304             :   }
    1305             : 
    1306             :   // Load the signature (SF) file and verify the signature.
    1307             :   // The .sf and .rsa files must have the same name apart from the extension.
    1308             : 
    1309           0 :   nsAutoString sfFilename(Substring(sigFilename, 0, sigFilename.Length() - 3)
    1310           0 :                           + NS_LITERAL_STRING("sf"));
    1311             : 
    1312           0 :   ScopedAutoSECItem sfBuffer;
    1313           0 :   Digest sfCalculatedDigest;
    1314           0 :   rv = LoadOneMetafile(metaDir, sfFilename, sfBuffer, &sfCalculatedDigest);
    1315           0 :   if (NS_FAILED(rv)) {
    1316           0 :     return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
    1317             :   }
    1318             : 
    1319           0 :   sigBuffer.type = siBuffer;
    1320           0 :   UniqueCERTCertList builtChain;
    1321           0 :   rv = VerifySignature(aTrustedRoot, sigBuffer, sfCalculatedDigest.get(),
    1322           0 :                        builtChain);
    1323           0 :   if (NS_FAILED(rv)) {
    1324           0 :     return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
    1325             :   }
    1326             : 
    1327             :   // Get the expected manifest hash from the signed .sf file
    1328             : 
    1329           0 :   nsAutoCString mfDigest;
    1330           0 :   rv = ParseSF(BitwiseCast<char*, unsigned char*>(sfBuffer.data), mfDigest);
    1331           0 :   if (NS_FAILED(rv)) {
    1332           0 :     return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
    1333             :   }
    1334             : 
    1335             :   // Load manifest (MF) file and verify signature
    1336             : 
    1337           0 :   nsAutoString mfFilename(NS_LITERAL_STRING("manifest.mf"));
    1338           0 :   ScopedAutoSECItem manifestBuffer;
    1339           0 :   Digest mfCalculatedDigest;
    1340           0 :   rv = LoadOneMetafile(metaDir, mfFilename, manifestBuffer, &mfCalculatedDigest);
    1341           0 :   if (NS_FAILED(rv)) {
    1342           0 :     return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
    1343             :   }
    1344             : 
    1345             :   nsDependentCSubstring calculatedDigest(
    1346           0 :     DigestToDependentString(mfCalculatedDigest));
    1347           0 :   if (!mfDigest.Equals(calculatedDigest)) {
    1348           0 :     return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
    1349             :   }
    1350             : 
    1351             :   // Parse manifest and verify signed hash of all listed files
    1352             : 
    1353             :   // Allocate the I/O buffer only once per JAR, instead of once per entry, in
    1354             :   // order to minimize malloc/free calls and in order to avoid fragmenting
    1355             :   // memory.
    1356           0 :   ScopedAutoSECItem buf(128 * 1024);
    1357             : 
    1358           0 :   nsTHashtable<nsStringHashKey> items;
    1359           0 :   rv = ParseMFUnpacked(BitwiseCast<char*, unsigned char*>(manifestBuffer.data),
    1360           0 :                        aDirectory, items, buf);
    1361           0 :   if (NS_FAILED(rv)){
    1362           0 :     return rv;
    1363             :   }
    1364             : 
    1365             :   // We've checked that everything listed in the manifest exists and is signed
    1366             :   // correctly. Now check on disk for extra (unsigned) files.
    1367             :   // Deletes found entries from items as it goes.
    1368           0 :   rv = CheckDirForUnsignedFiles(aDirectory, EmptyString(), items,
    1369           0 :                                 sigFilename, sfFilename, mfFilename);
    1370           0 :   if (NS_FAILED(rv)) {
    1371           0 :     return rv;
    1372             :   }
    1373             : 
    1374             :   // We verified that every entry that we require to be signed is signed. But,
    1375             :   // were there any missing entries--that is, entries that are mentioned in the
    1376             :   // manifest but missing from the directory tree? (There shouldn't be given
    1377             :   // ParseMFUnpacked() checking them all, but it's a cheap sanity check.)
    1378           0 :   if (items.Count() != 0) {
    1379           0 :     return NS_ERROR_SIGNED_JAR_ENTRY_MISSING;
    1380             :   }
    1381             : 
    1382             :   // Return the signer's certificate to the reader if they want it.
    1383             :   // XXX: We should return an nsIX509CertList with the whole validated chain.
    1384           0 :   if (aSignerCert) {
    1385           0 :     CERTCertListNode* signerCertNode = CERT_LIST_HEAD(builtChain);
    1386           0 :     if (!signerCertNode || CERT_LIST_END(signerCertNode, builtChain) ||
    1387           0 :         !signerCertNode->cert) {
    1388           0 :       return NS_ERROR_FAILURE;
    1389             :     }
    1390             :     nsCOMPtr<nsIX509Cert> signerCert =
    1391           0 :       nsNSSCertificate::Create(signerCertNode->cert);
    1392           0 :     NS_ENSURE_TRUE(signerCert, NS_ERROR_OUT_OF_MEMORY);
    1393           0 :     signerCert.forget(aSignerCert);
    1394             :   }
    1395             : 
    1396           0 :   return NS_OK;
    1397             : }
    1398             : 
    1399           0 : class VerifySignedDirectoryTask final : public CryptoTask
    1400             : {
    1401             : public:
    1402           0 :   VerifySignedDirectoryTask(AppTrustedRoot aTrustedRoot, nsIFile* aUnpackedJar,
    1403             :                             nsIVerifySignedDirectoryCallback* aCallback)
    1404           0 :     : mTrustedRoot(aTrustedRoot)
    1405             :     , mDirectory(aUnpackedJar)
    1406             :     , mCallback(new nsMainThreadPtrHolder<nsIVerifySignedDirectoryCallback>(
    1407           0 :         "VerifySignedDirectoryTask::mCallback", aCallback))
    1408             :   {
    1409           0 :   }
    1410             : 
    1411             : private:
    1412           0 :   virtual nsresult CalculateResult() override
    1413             :   {
    1414           0 :     return VerifySignedDirectory(mTrustedRoot,
    1415             :                                  mDirectory,
    1416           0 :                                  getter_AddRefs(mSignerCert));
    1417             :   }
    1418             : 
    1419             :   // This class doesn't directly hold NSS resources so there's nothing that
    1420             :   // needs to be released
    1421           0 :   virtual void ReleaseNSSResources() override { }
    1422             : 
    1423           0 :   virtual void CallCallback(nsresult rv) override
    1424             :   {
    1425           0 :     (void) mCallback->VerifySignedDirectoryFinished(rv, mSignerCert);
    1426           0 :   }
    1427             : 
    1428             :   const AppTrustedRoot mTrustedRoot;
    1429             :   const nsCOMPtr<nsIFile> mDirectory;
    1430             :   nsMainThreadPtrHandle<nsIVerifySignedDirectoryCallback> mCallback;
    1431             :   nsCOMPtr<nsIX509Cert> mSignerCert; // out
    1432             : };
    1433             : 
    1434             : NS_IMETHODIMP
    1435           0 : nsNSSCertificateDB::VerifySignedDirectoryAsync(
    1436             :   AppTrustedRoot aTrustedRoot, nsIFile* aUnpackedJar,
    1437             :   nsIVerifySignedDirectoryCallback* aCallback)
    1438             : {
    1439           0 :   NS_ENSURE_ARG_POINTER(aUnpackedJar);
    1440           0 :   NS_ENSURE_ARG_POINTER(aCallback);
    1441             :   RefPtr<VerifySignedDirectoryTask> task(new VerifySignedDirectoryTask(aTrustedRoot,
    1442             :                                                                        aUnpackedJar,
    1443           0 :                                                                        aCallback));
    1444           0 :   return task->Dispatch("UnpackedJar");
    1445             : }

Generated by: LCOV version 1.13