security/apps/AppSignatureVerification.cpp

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

     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/. */
     7 #ifdef MOZ_LOGGING
     8 #define FORCE_PR_LOG 1
     9 #endif
    11 #include "nsNSSCertificateDB.h"
    13 #include "pkix/pkix.h"
    14 #include "mozilla/RefPtr.h"
    15 #include "CryptoTask.h"
    16 #include "AppTrustDomain.h"
    17 #include "nsComponentManagerUtils.h"
    18 #include "nsCOMPtr.h"
    19 #include "nsHashKeys.h"
    20 #include "nsIFile.h"
    21 #include "nsIInputStream.h"
    22 #include "nsIStringEnumerator.h"
    23 #include "nsIZipReader.h"
    24 #include "nsNSSCertificate.h"
    25 #include "nsProxyRelease.h"
    26 #include "nsString.h"
    27 #include "nsTHashtable.h"
    28 #include "ScopedNSSTypes.h"
    30 #include "base64.h"
    31 #include "certdb.h"
    32 #include "secmime.h"
    33 #include "plstr.h"
    34 #include "prlog.h"
    36 using namespace mozilla::pkix;
    37 using namespace mozilla;
    38 using namespace mozilla::psm;
    40 #ifdef PR_LOGGING
    41 extern PRLogModuleInfo* gPIPNSSLog;
    42 #endif
    44 namespace {
    46 // Finds exactly one (signature metadata) entry that matches the given
    47 // search pattern, and then load it. Fails if there are no matches or if
    48 // there is more than one match. If bugDigest is not null then on success
    49 // bufDigest will contain the SHA-1 digeset of the entry.
    50 nsresult
    51 FindAndLoadOneEntry(nsIZipReader * zip,
    52                     const nsACString & searchPattern,
    53                     /*out*/ nsACString & filename,
    54                     /*out*/ SECItem & buf,
    55                     /*optional, out*/ Digest * bufDigest)
    56 {
    57   nsCOMPtr<nsIUTF8StringEnumerator> files;
    58   nsresult rv = zip->FindEntries(searchPattern, getter_AddRefs(files));
    59   if (NS_FAILED(rv) || !files) {
    60     return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
    61   }
    63   bool more;
    64   rv = files->HasMore(&more);
    65   NS_ENSURE_SUCCESS(rv, rv);
    66   if (!more) {
    67     return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
    68   }
    70   rv = files->GetNext(filename);
    71   NS_ENSURE_SUCCESS(rv, rv);
    73   // Check if there is more than one match, if so then error!
    74   rv = files->HasMore(&more);
    75   NS_ENSURE_SUCCESS(rv, rv);
    76   if (more) {
    77     return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
    78   }
    80   nsCOMPtr<nsIInputStream> stream;
    81   rv = zip->GetInputStream(filename, getter_AddRefs(stream));
    82   NS_ENSURE_SUCCESS(rv, rv);
    84   // The size returned by Available() might be inaccurate so we need to check
    85   // that Available() matches up with the actual length of the file.
    86   uint64_t len64;
    87   rv = stream->Available(&len64);
    88   NS_ENSURE_SUCCESS(rv, rv);
    91   // Cap the maximum accepted size of signature-related files at 1MB (which is
    92   // still crazily huge) to avoid OOM. The uncompressed length of an entry can be
    93   // hundreds of times larger than the compressed version, especially if
    94   // someone has speifically crafted the entry to cause OOM or to consume
    95   // massive amounts of disk space.
    96   //
    97   // Also, keep in mind bug 164695 and that we must leave room for
    98   // null-terminating the buffer.
    99   static const uint32_t MAX_LENGTH = 1024 * 1024;
   100   static_assert(MAX_LENGTH < UINT32_MAX, "MAX_LENGTH < UINT32_MAX");
   101   NS_ENSURE_TRUE(len64 < MAX_LENGTH, NS_ERROR_FILE_CORRUPTED);
   102   NS_ENSURE_TRUE(len64 < UINT32_MAX, NS_ERROR_FILE_CORRUPTED); // bug 164695
   103   SECITEM_AllocItem(buf, static_cast<uint32_t>(len64 + 1));
   105   // buf.len == len64 + 1. We attempt to read len64 + 1 bytes instead of len64,
   106   // so that we can check whether the metadata in the ZIP for the entry is
   107   // incorrect.
   108   uint32_t bytesRead;
   109   rv = stream->Read(char_ptr_cast(buf.data), buf.len, &bytesRead);
   110   NS_ENSURE_SUCCESS(rv, rv);
   111   if (bytesRead != len64) {
   112     return NS_ERROR_SIGNED_JAR_ENTRY_INVALID;
   113   }
   115   buf.data[buf.len - 1] = 0; // null-terminate
   117   if (bufDigest) {
   118     rv = bufDigest->DigestBuf(SEC_OID_SHA1, buf.data, buf.len - 1);
   119     NS_ENSURE_SUCCESS(rv, rv);
   120   }
   122   return NS_OK;
   123 }
   125 // Verify the digest of an entry. We avoid loading the entire entry into memory
   126 // at once, which would require memory in proportion to the size of the largest
   127 // entry. Instead, we require only a small, fixed amount of memory.
   128 //
   129 // @param digestFromManifest The digest that we're supposed to check the file's
   130 //                           contents against, from the manifest
   131 // @param buf A scratch buffer that we use for doing the I/O, which must have
   132 //            already been allocated. The size of this buffer is the unit
   133 //            size of our I/O.
   134 nsresult
   135 VerifyEntryContentDigest(nsIZipReader * zip, const nsACString & aFilename,
   136                          const SECItem & digestFromManifest, SECItem & buf)
   137 {
   138   MOZ_ASSERT(buf.len > 0);
   139   if (digestFromManifest.len != SHA1_LENGTH)
   140     return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
   142   nsresult rv;
   144   nsCOMPtr<nsIInputStream> stream;
   145   rv = zip->GetInputStream(aFilename, getter_AddRefs(stream));
   146   if (NS_FAILED(rv)) {
   147     return NS_ERROR_SIGNED_JAR_ENTRY_MISSING;
   148   }
   150   uint64_t len64;
   151   rv = stream->Available(&len64);
   152   NS_ENSURE_SUCCESS(rv, rv);
   153   if (len64 > UINT32_MAX) {
   154     return NS_ERROR_SIGNED_JAR_ENTRY_TOO_LARGE;
   155   }
   157   ScopedPK11Context digestContext(PK11_CreateDigestContext(SEC_OID_SHA1));
   158   if (!digestContext) {
   159     return PRErrorCode_to_nsresult(PR_GetError());
   160   }
   162   rv = MapSECStatus(PK11_DigestBegin(digestContext));
   163   NS_ENSURE_SUCCESS(rv, rv);
   165   uint64_t totalBytesRead = 0;
   166   for (;;) {
   167     uint32_t bytesRead;
   168     rv = stream->Read(char_ptr_cast(buf.data), buf.len, &bytesRead);
   169     NS_ENSURE_SUCCESS(rv, rv);
   171     if (bytesRead == 0) {
   172       break; // EOF
   173     }
   175     totalBytesRead += bytesRead;
   176     if (totalBytesRead >= UINT32_MAX) {
   177       return NS_ERROR_SIGNED_JAR_ENTRY_TOO_LARGE;
   178     }
   180     rv = MapSECStatus(PK11_DigestOp(digestContext, buf.data, bytesRead));
   181     NS_ENSURE_SUCCESS(rv, rv);
   182   }
   184   if (totalBytesRead != len64) {
   185     // The metadata we used for Available() doesn't match the actual size of
   186     // the entry.
   187     return NS_ERROR_SIGNED_JAR_ENTRY_INVALID;
   188   }
   190   // Verify that the digests match.
   191   Digest digest;
   192   rv = digest.End(SEC_OID_SHA1, digestContext);
   193   NS_ENSURE_SUCCESS(rv, rv);
   195   if (SECITEM_CompareItem(&digestFromManifest, &digest.get()) != SECEqual) {
   196     return NS_ERROR_SIGNED_JAR_MODIFIED_ENTRY;
   197   }
   199   return NS_OK;
   200 }
   202 // On input, nextLineStart is the start of the current line. On output,
   203 // nextLineStart is the start of the next line.
   204 nsresult
   205 ReadLine(/*in/out*/ const char* & nextLineStart, /*out*/ nsCString & line,
   206          bool allowContinuations = true)
   207 {
   208   line.Truncate();
   209   size_t previousLength = 0;
   210   size_t currentLength = 0;
   211   for (;;) {
   212     const char* eol = PL_strpbrk(nextLineStart, "\r\n");
   214     if (!eol) { // Reached end of file before newline
   215       eol = nextLineStart + strlen(nextLineStart);
   216     }
   218     previousLength = currentLength;
   219     line.Append(nextLineStart, eol - nextLineStart);
   220     currentLength = line.Length();
   222     // The spec says "No line may be longer than 72 bytes (not characters)"
   223     // in its UTF8-encoded form.
   224     static const size_t lineLimit = 72;
   225     if (currentLength - previousLength > lineLimit) {
   226       return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
   227     }
   229     // The spec says: "Implementations should support 65535-byte
   230     // (not character) header values..."
   231     if (currentLength > 65535) {
   232       return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
   233     }
   235     if (*eol == '\r') {
   236       ++eol;
   237     }
   238     if (*eol == '\n') {
   239       ++eol;
   240     }
   242     nextLineStart = eol;
   244     if (*eol != ' ') {
   245       // not a continuation
   246       return NS_OK;
   247     }
   249     // continuation
   250     if (!allowContinuations) {
   251       return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
   252     }
   254     ++nextLineStart; // skip space and keep appending
   255   }
   256 }
   258 // The header strings are defined in the JAR specification.
   259 #define JAR_MF_SEARCH_STRING "(M|/M)ETA-INF/(M|m)(ANIFEST|anifest).(MF|mf)$"
   260 #define JAR_SF_SEARCH_STRING "(M|/M)ETA-INF/*.(SF|sf)$"
   261 #define JAR_RSA_SEARCH_STRING "(M|/M)ETA-INF/*.(RSA|rsa)$"
   262 #define JAR_MF_HEADER "Manifest-Version: 1.0"
   263 #define JAR_SF_HEADER "Signature-Version: 1.0"
   265 nsresult
   266 ParseAttribute(const nsAutoCString & curLine,
   267                /*out*/ nsAutoCString & attrName,
   268                /*out*/ nsAutoCString & attrValue)
   269 {
   270   // Find the colon that separates the name from the value.
   271   int32_t colonPos = curLine.FindChar(':');
   272   if (colonPos == kNotFound) {
   273     return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
   274   }
   276   // set attrName to the name, skipping spaces between the name and colon
   277   int32_t nameEnd = colonPos;
   278   for (;;) {
   279     if (nameEnd == 0) {
   280       return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; // colon with no name
   281     }
   282     if (curLine[nameEnd - 1] != ' ')
   283       break;
   284     --nameEnd;
   285   }
   286   curLine.Left(attrName, nameEnd);
   288   // Set attrValue to the value, skipping spaces between the colon and the
   289   // value. The value may be empty.
   290   int32_t valueStart = colonPos + 1;
   291   int32_t curLineLength = curLine.Length();
   292   while (valueStart != curLineLength && curLine[valueStart] == ' ') {
   293     ++valueStart;
   294   }
   295   curLine.Right(attrValue, curLineLength - valueStart);
   297   return NS_OK;
   298 }
   300 // Parses the version line of the MF or SF header.
   301 nsresult
   302 CheckManifestVersion(const char* & nextLineStart,
   303                      const nsACString & expectedHeader)
   304 {
   305   // The JAR spec says: "Manifest-Version and Signature-Version must be first,
   306   // and in exactly that case (so that they can be recognized easily as magic
   307   // strings)."
   308   nsAutoCString curLine;
   309   nsresult rv = ReadLine(nextLineStart, curLine, false);
   310   if (NS_FAILED(rv)) {
   311     return rv;
   312   }
   313   if (!curLine.Equals(expectedHeader)) {
   314     return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
   315   }
   316   return NS_OK;
   317 }
   319 // Parses a signature file (SF) as defined in the JDK 8 JAR Specification.
   320 //
   321 // The SF file *must* contain exactly one SHA1-Digest-Manifest attribute in
   322 // the main section. All other sections are ignored. This means that this will
   323 // NOT parse old-style signature files that have separate digests per entry.
   324 // The JDK8 x-Digest-Manifest variant is better because:
   325 //
   326 //   (1) It allows us to follow the principle that we should minimize the
   327 //       processing of data that we do before we verify its signature. In
   328 //       particular, with the x-Digest-Manifest style, we can verify the digest
   329 //       of MANIFEST.MF before we parse it, which prevents malicious JARs
   330 //       exploiting our MANIFEST.MF parser.
   331 //   (2) It is more time-efficient and space-efficient to have one
   332 //       x-Digest-Manifest instead of multiple x-Digest values.
   333 //
   334 // In order to get benefit (1), we do NOT implement the fallback to the older
   335 // mechanism as the spec requires/suggests. Also, for simplity's sake, we only
   336 // support exactly one SHA1-Digest-Manifest attribute, and no other
   337 // algorithms.
   338 //
   339 // filebuf must be null-terminated. On output, mfDigest will contain the
   340 // decoded value of SHA1-Digest-Manifest.
   341 nsresult
   342 ParseSF(const char* filebuf, /*out*/ SECItem & mfDigest)
   343 {
   344   nsresult rv;
   346   const char* nextLineStart = filebuf;
   347   rv = CheckManifestVersion(nextLineStart, NS_LITERAL_CSTRING(JAR_SF_HEADER));
   348   if (NS_FAILED(rv))
   349     return rv;
   351   // Find SHA1-Digest-Manifest
   352   for (;;) {
   353     nsAutoCString curLine;
   354     rv = ReadLine(nextLineStart, curLine);
   355     if (NS_FAILED(rv)) {
   356       return rv;
   357     }
   359     if (curLine.Length() == 0) {
   360       // End of main section (blank line or end-of-file), and no
   361       // SHA1-Digest-Manifest found.
   362       return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
   363     }
   365     nsAutoCString attrName;
   366     nsAutoCString attrValue;
   367     rv = ParseAttribute(curLine, attrName, attrValue);
   368     if (NS_FAILED(rv)) {
   369       return rv;
   370     }
   372     if (attrName.LowerCaseEqualsLiteral("sha1-digest-manifest")) {
   373       rv = MapSECStatus(ATOB_ConvertAsciiToItem(&mfDigest, attrValue.get()));
   374       if (NS_FAILED(rv)) {
   375         return rv;
   376       }
   378       // There could be multiple SHA1-Digest-Manifest attributes, which
   379       // would be an error, but it's better to just skip any erroneous
   380       // duplicate entries rather than trying to detect them, because:
   381       //
   382       //   (1) It's simpler, and simpler generally means more secure
   383       //   (2) An attacker can't make us accept a JAR we would otherwise
   384       //       reject just by adding additional SHA1-Digest-Manifest
   385       //       attributes.
   386       break;
   387     }
   389     // ignore unrecognized attributes
   390   }
   392   return NS_OK;
   393 }
   395 // Parses MANIFEST.MF. The filenames of all entries will be returned in
   396 // mfItems. buf must be a pre-allocated scratch buffer that is used for doing
   397 // I/O.
   398 nsresult
   399 ParseMF(const char* filebuf, nsIZipReader * zip,
   400         /*out*/ nsTHashtable<nsCStringHashKey> & mfItems,
   401         ScopedAutoSECItem & buf)
   402 {
   403   nsresult rv;
   405   const char* nextLineStart = filebuf;
   407   rv = CheckManifestVersion(nextLineStart, NS_LITERAL_CSTRING(JAR_MF_HEADER));
   408   if (NS_FAILED(rv)) {
   409     return rv;
   410   }
   412   // Skip the rest of the header section, which ends with a blank line.
   413   {
   414     nsAutoCString line;
   415     do {
   416       rv = ReadLine(nextLineStart, line);
   417       if (NS_FAILED(rv)) {
   418         return rv;
   419       }
   420     } while (line.Length() > 0);
   422     // Manifest containing no file entries is OK, though useless.
   423     if (*nextLineStart == '\0') {
   424       return NS_OK;
   425     }
   426   }
   428   nsAutoCString curItemName;
   429   ScopedAutoSECItem digest;
   431   for (;;) {
   432     nsAutoCString curLine;
   433     rv = ReadLine(nextLineStart, curLine);
   434     NS_ENSURE_SUCCESS(rv, rv);
   436     if (curLine.Length() == 0) {
   437       // end of section (blank line or end-of-file)
   439       if (curItemName.Length() == 0) {
   440         // '...Each section must start with an attribute with the name as
   441         // "Name",...', so every section must have a Name attribute.
   442         return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
   443       }
   445       if (digest.len == 0) {
   446         // We require every entry to have a digest, since we require every
   447         // entry to be signed and we don't allow duplicate entries.
   448         return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
   449       }
   451       if (mfItems.Contains(curItemName)) {
   452         // Duplicate entry
   453         return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
   454       }
   456       // Verify that the entry's content digest matches the digest from this
   457       // MF section.
   458       rv = VerifyEntryContentDigest(zip, curItemName, digest, buf);
   459       if (NS_FAILED(rv))
   460         return rv;
   462       mfItems.PutEntry(curItemName);
   464       if (*nextLineStart == '\0') // end-of-file
   465         break;
   467       // reset so we know we haven't encountered either of these for the next
   468       // item yet.
   469       curItemName.Truncate();
   470       digest.reset();
   472       continue; // skip the rest of the loop below
   473     }
   475     nsAutoCString attrName;
   476     nsAutoCString attrValue;
   477     rv = ParseAttribute(curLine, attrName, attrValue);
   478     if (NS_FAILED(rv)) {
   479       return rv;
   480     }
   482     // Lines to look for:
   484     // (1) Digest:
   485     if (attrName.LowerCaseEqualsLiteral("sha1-digest"))
   486     {
   487       if (digest.len > 0) // multiple SHA1 digests in section
   488         return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
   490       rv = MapSECStatus(ATOB_ConvertAsciiToItem(&digest, attrValue.get()));
   491       if (NS_FAILED(rv))
   492         return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
   494       continue;
   495     }
   497     // (2) Name: associates this manifest section with a file in the jar.
   498     if (attrName.LowerCaseEqualsLiteral("name"))
   499     {
   500       if (MOZ_UNLIKELY(curItemName.Length() > 0)) // multiple names in section
   501         return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
   503       if (MOZ_UNLIKELY(attrValue.Length() == 0))
   504         return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
   506       curItemName = attrValue;
   508       continue;
   509     }
   511     // (3) Magic: the only other must-understand attribute
   512     if (attrName.LowerCaseEqualsLiteral("magic")) {
   513       // We don't understand any magic, so we can't verify an entry that
   514       // requires magic. Since we require every entry to have a valid
   515       // signature, we have no choice but to reject the entry.
   516       return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
   517     }
   519     // unrecognized attributes must be ignored
   520   }
   522   return NS_OK;
   523 }
   525 nsresult
   526 VerifySignature(AppTrustedRoot trustedRoot,
   527                 const SECItem& buffer, const SECItem& detachedDigest,
   528         /*out*/ mozilla::pkix::ScopedCERTCertList& builtChain)
   529 {
   530   mozilla::pkix::ScopedPtr<NSSCMSMessage, NSS_CMSMessage_Destroy>
   531     cmsMsg(NSS_CMSMessage_CreateFromDER(const_cast<SECItem*>(&buffer), nullptr,
   532                                         nullptr, nullptr, nullptr, nullptr,
   533                                         nullptr));
   534   if (!cmsMsg) {
   535     return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING;
   536   }
   538   if (!NSS_CMSMessage_IsSigned(cmsMsg.get())) {
   539     PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("CMS message isn't signed"));
   540     return NS_ERROR_CMS_VERIFY_NOT_SIGNED;
   541   }
   543   NSSCMSContentInfo* cinfo = NSS_CMSMessage_ContentLevel(cmsMsg.get(), 0);
   544   if (!cinfo) {
   545     return NS_ERROR_CMS_VERIFY_NO_CONTENT_INFO;
   546   }
   548   // signedData is non-owning
   549   NSSCMSSignedData* signedData =
   550     reinterpret_cast<NSSCMSSignedData*>(NSS_CMSContentInfo_GetContent(cinfo));
   551   if (!signedData) {
   552     return NS_ERROR_CMS_VERIFY_NO_CONTENT_INFO;
   553   }
   555   // Set digest value.
   556   if (NSS_CMSSignedData_SetDigestValue(signedData, SEC_OID_SHA1,
   557                                        const_cast<SECItem*>(&detachedDigest))) {
   558     return NS_ERROR_CMS_VERIFY_BAD_DIGEST;
   559   }
   561   // Parse the certificates into CERTCertificate objects held in memory, so that
   562   // AppTrustDomain will be able to find them during path building.
   563   mozilla::pkix::ScopedCERTCertList certs(CERT_NewCertList());
   564   if (!certs) {
   565     return NS_ERROR_OUT_OF_MEMORY;
   566   }
   567   if (signedData->rawCerts) {
   568     for (size_t i = 0; signedData->rawCerts[i]; ++i) {
   569       mozilla::pkix::ScopedCERTCertificate
   570         cert(CERT_NewTempCertificate(CERT_GetDefaultCertDB(),
   571                                      signedData->rawCerts[i], nullptr, false,
   572                                      true));
   573       // Skip certificates that fail to parse
   574       if (cert) {
   575         if (CERT_AddCertToListTail(certs.get(), cert.get()) == SECSuccess) {
   576           cert.release(); // ownership transfered
   577         } else {
   578           return NS_ERROR_OUT_OF_MEMORY;
   579         }
   580       }
   581     }
   582   }
   584   // Get the end-entity certificate.
   585   int numSigners = NSS_CMSSignedData_SignerInfoCount(signedData);
   586   if (NS_WARN_IF(numSigners != 1)) {
   587     return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING;
   588   }
   589   // signer is non-owning.
   590   NSSCMSSignerInfo* signer = NSS_CMSSignedData_GetSignerInfo(signedData, 0);
   591   if (NS_WARN_IF(!signer)) {
   592     return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING;
   593   }
   594   // cert is signerCert
   595   CERTCertificate* signerCert =
   596     NSS_CMSSignerInfo_GetSigningCertificate(signer, CERT_GetDefaultCertDB());
   597   if (!signerCert) {
   598     return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING;
   599   }
   601   // Verify certificate.
   602   AppTrustDomain trustDomain(nullptr); // TODO: null pinArg
   603   if (trustDomain.SetTrustedRoot(trustedRoot) != SECSuccess) {
   604     return MapSECStatus(SECFailure);
   605   }
   606   if (BuildCertChain(trustDomain, signerCert, PR_Now(), MustBeEndEntity,
   607                      KeyUsage::digitalSignature,
   608                      SEC_OID_EXT_KEY_USAGE_CODE_SIGN,
   609                      SEC_OID_X509_ANY_POLICY, nullptr, builtChain)
   610         != SECSuccess) {
   611     return MapSECStatus(SECFailure);
   612   }
   614   // See NSS_CMSContentInfo_GetContentTypeOID, which isn't exported from NSS.
   615   SECOidData* contentTypeOidData =
   616     SECOID_FindOID(&signedData->contentInfo.contentType);
   617   if (!contentTypeOidData) {
   618     return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING;
   619   }
   621   return MapSECStatus(NSS_CMSSignerInfo_Verify(signer,
   622                          const_cast<SECItem*>(&detachedDigest),
   623                          &contentTypeOidData->oid));
   624 }
   626 NS_IMETHODIMP
   627 OpenSignedAppFile(AppTrustedRoot aTrustedRoot, nsIFile* aJarFile,
   628                   /*out, optional */ nsIZipReader** aZipReader,
   629                   /*out, optional */ nsIX509Cert3** aSignerCert)
   630 {
   631   NS_ENSURE_ARG_POINTER(aJarFile);
   633   if (aZipReader) {
   634     *aZipReader = nullptr;
   635   }
   637   if (aSignerCert) {
   638     *aSignerCert = nullptr;
   639   }
   641   nsresult rv;
   643   static NS_DEFINE_CID(kZipReaderCID, NS_ZIPREADER_CID);
   644   nsCOMPtr<nsIZipReader> zip = do_CreateInstance(kZipReaderCID, &rv);
   645   NS_ENSURE_SUCCESS(rv, rv);
   647   rv = zip->Open(aJarFile);
   648   NS_ENSURE_SUCCESS(rv, rv);
   650   // Signature (RSA) file
   651   nsAutoCString sigFilename;
   652   ScopedAutoSECItem sigBuffer;
   653   rv = FindAndLoadOneEntry(zip, nsLiteralCString(JAR_RSA_SEARCH_STRING),
   654                            sigFilename, sigBuffer, nullptr);
   655   if (NS_FAILED(rv)) {
   656     return NS_ERROR_SIGNED_JAR_NOT_SIGNED;
   657   }
   659   // Signature (SF) file
   660   nsAutoCString sfFilename;
   661   ScopedAutoSECItem sfBuffer;
   662   Digest sfCalculatedDigest;
   663   rv = FindAndLoadOneEntry(zip, NS_LITERAL_CSTRING(JAR_SF_SEARCH_STRING),
   664                            sfFilename, sfBuffer, &sfCalculatedDigest);
   665   if (NS_FAILED(rv)) {
   666     return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
   667   }
   669   sigBuffer.type = siBuffer;
   670   mozilla::pkix::ScopedCERTCertList builtChain;
   671   rv = VerifySignature(aTrustedRoot, sigBuffer, sfCalculatedDigest.get(),
   672                        builtChain);
   673   if (NS_FAILED(rv)) {
   674     return rv;
   675   }
   677   ScopedAutoSECItem mfDigest;
   678   rv = ParseSF(char_ptr_cast(sfBuffer.data), mfDigest);
   679   if (NS_FAILED(rv)) {
   680     return rv;
   681   }
   683   // Manifest (MF) file
   684   nsAutoCString mfFilename;
   685   ScopedAutoSECItem manifestBuffer;
   686   Digest mfCalculatedDigest;
   687   rv = FindAndLoadOneEntry(zip, NS_LITERAL_CSTRING(JAR_MF_SEARCH_STRING),
   688                            mfFilename, manifestBuffer, &mfCalculatedDigest);
   689   if (NS_FAILED(rv)) {
   690     return rv;
   691   }
   693   if (SECITEM_CompareItem(&mfDigest, &mfCalculatedDigest.get()) != SECEqual) {
   694     return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
   695   }
   697   // Allocate the I/O buffer only once per JAR, instead of once per entry, in
   698   // order to minimize malloc/free calls and in order to avoid fragmenting
   699   // memory.
   700   ScopedAutoSECItem buf(128 * 1024);
   702   nsTHashtable<nsCStringHashKey> items;
   704   rv = ParseMF(char_ptr_cast(manifestBuffer.data), zip, items, buf);
   705   if (NS_FAILED(rv)) {
   706     return rv;
   707   }
   709   // Verify every entry in the file.
   710   nsCOMPtr<nsIUTF8StringEnumerator> entries;
   711   rv = zip->FindEntries(EmptyCString(), getter_AddRefs(entries));
   712   if (NS_SUCCEEDED(rv) && !entries) {
   713     rv = NS_ERROR_UNEXPECTED;
   714   }
   715   if (NS_FAILED(rv)) {
   716     return rv;
   717   }
   719   for (;;) {
   720     bool hasMore;
   721     rv = entries->HasMore(&hasMore);
   722     NS_ENSURE_SUCCESS(rv, rv);
   724     if (!hasMore) {
   725       break;
   726     }
   728     nsAutoCString entryFilename;
   729     rv = entries->GetNext(entryFilename);
   730     NS_ENSURE_SUCCESS(rv, rv);
   732     PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("Verifying digests for %s",
   733            entryFilename.get()));
   735     // The files that comprise the signature mechanism are not covered by the
   736     // signature.
   737     //
   738     // XXX: This is OK for a single signature, but doesn't work for
   739     // multiple signatures, because the metadata for the other signatures
   740     // is not signed either.
   741     if (entryFilename == mfFilename ||
   742         entryFilename == sfFilename ||
   743         entryFilename == sigFilename) {
   744       continue;
   745     }
   747     if (entryFilename.Length() == 0) {
   748       return NS_ERROR_SIGNED_JAR_ENTRY_INVALID;
   749     }
   751     // Entries with names that end in "/" are directory entries, which are not
   752     // signed.
   753     //
   754     // XXX: As long as we don't unpack the JAR into the filesystem, the "/"
   755     // entries are harmless. But, it is not clear what the security
   756     // implications of directory entries are if/when we were to unpackage the
   757     // JAR into the filesystem.
   758     if (entryFilename[entryFilename.Length() - 1] == '/') {
   759       continue;
   760     }
   762     nsCStringHashKey * item = items.GetEntry(entryFilename);
   763     if (!item) {
   764       return NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY;
   765     }
   767     // Remove the item so we can check for leftover items later
   768     items.RemoveEntry(entryFilename);
   769   }
   771   // We verified that every entry that we require to be signed is signed. But,
   772   // were there any missing entries--that is, entries that are mentioned in the
   773   // manifest but missing from the archive?
   774   if (items.Count() != 0) {
   775     return NS_ERROR_SIGNED_JAR_ENTRY_MISSING;
   776   }
   778   // Return the reader to the caller if they want it
   779   if (aZipReader) {
   780     zip.forget(aZipReader);
   781   }
   783   // Return the signer's certificate to the reader if they want it.
   784   // XXX: We should return an nsIX509CertList with the whole validated chain,
   785   //      but we can't do that until we switch to libpkix.
   786   if (aSignerCert) {
   787     MOZ_ASSERT(CERT_LIST_HEAD(builtChain));
   788     nsCOMPtr<nsIX509Cert3> signerCert =
   789       nsNSSCertificate::Create(CERT_LIST_HEAD(builtChain)->cert);
   790     NS_ENSURE_TRUE(signerCert, NS_ERROR_OUT_OF_MEMORY);
   791     signerCert.forget(aSignerCert);
   792   }
   794   return NS_OK;
   795 }
   797 class OpenSignedAppFileTask MOZ_FINAL : public CryptoTask
   798 {
   799 public:
   800   OpenSignedAppFileTask(AppTrustedRoot aTrustedRoot, nsIFile* aJarFile,
   801                         nsIOpenSignedAppFileCallback* aCallback)
   802     : mTrustedRoot(aTrustedRoot)
   803     , mJarFile(aJarFile)
   804     , mCallback(new nsMainThreadPtrHolder<nsIOpenSignedAppFileCallback>(aCallback))
   805   {
   806   }
   808 private:
   809   virtual nsresult CalculateResult() MOZ_OVERRIDE
   810   {
   811     return OpenSignedAppFile(mTrustedRoot, mJarFile,
   812                              getter_AddRefs(mZipReader),
   813                              getter_AddRefs(mSignerCert));
   814   }
   816   // nsNSSCertificate implements nsNSSShutdownObject, so there's nothing that
   817   // needs to be released
   818   virtual void ReleaseNSSResources() { }
   820   virtual void CallCallback(nsresult rv)
   821   {
   822     (void) mCallback->OpenSignedAppFileFinished(rv, mZipReader, mSignerCert);
   823   }
   825   const AppTrustedRoot mTrustedRoot;
   826   const nsCOMPtr<nsIFile> mJarFile;
   827   nsMainThreadPtrHandle<nsIOpenSignedAppFileCallback> mCallback;
   828   nsCOMPtr<nsIZipReader> mZipReader; // out
   829   nsCOMPtr<nsIX509Cert3> mSignerCert; // out
   830 };
   832 } // unnamed namespace
   834 NS_IMETHODIMP
   835 nsNSSCertificateDB::OpenSignedAppFileAsync(
   836   AppTrustedRoot aTrustedRoot, nsIFile* aJarFile,
   837   nsIOpenSignedAppFileCallback* aCallback)
   838 {
   839   NS_ENSURE_ARG_POINTER(aJarFile);
   840   NS_ENSURE_ARG_POINTER(aCallback);
   841   RefPtr<OpenSignedAppFileTask> task(new OpenSignedAppFileTask(aTrustedRoot,
   842                                                                aJarFile,
   843                                                                aCallback));
   844   return task->Dispatch("SignedJAR");
   845 }

mercurial