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