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 "SharedCertVerifier.h"
14 #include "certdb.h"
15 #include "cms.h"
16 #include "cosec.h"
17 #include "mozilla/Base64.h"
18 #include "mozilla/Casting.h"
19 #include "mozilla/Logging.h"
20 #include "mozilla/Preferences.h"
21 #include "mozilla/RefPtr.h"
22 #include "mozilla/UniquePtr.h"
23 #include "mozilla/Unused.h"
24 #include "nsCOMPtr.h"
25 #include "nsComponentManagerUtils.h"
26 #include "nsDependentString.h"
27 #include "nsHashKeys.h"
28 #include "nsIFile.h"
29 #include "nsIInputStream.h"
30 #include "nsIStringEnumerator.h"
31 #include "nsIZipReader.h"
32 #include "nsNSSCertificate.h"
33 #include "nsNetUtil.h"
34 #include "nsProxyRelease.h"
35 #include "nsString.h"
36 #include "nsTHashtable.h"
37 #include "mozpkix/pkix.h"
38 #include "mozpkix/pkixnss.h"
39 #include "mozpkix/pkixutil.h"
40 #include "secerr.h"
41 #include "secmime.h"
42 
43 using namespace mozilla::pkix;
44 using namespace mozilla;
45 using namespace mozilla::psm;
46 
47 extern mozilla::LazyLogModule gPIPNSSLog;
48 
49 namespace {
50 
51 // A convenient way to pair the bytes of a digest with the algorithm that
52 // purportedly produced those bytes. Only SHA-1 and SHA-256 are supported.
53 struct DigestWithAlgorithm {
ValidateLength__anondb7a92b00111::DigestWithAlgorithm54   nsresult ValidateLength() const {
55     size_t hashLen;
56     switch (mAlgorithm) {
57       case SEC_OID_SHA256:
58         hashLen = SHA256_LENGTH;
59         break;
60       case SEC_OID_SHA1:
61         hashLen = SHA1_LENGTH;
62         break;
63       default:
64         MOZ_ASSERT_UNREACHABLE(
65             "unsupported hash type in DigestWithAlgorithm::ValidateLength");
66         return NS_ERROR_FAILURE;
67     }
68     if (mDigest.Length() != hashLen) {
69       return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
70     }
71     return NS_OK;
72   }
73 
74   nsAutoCString mDigest;
75   SECOidTag mAlgorithm;
76 };
77 
78 // The digest must have a lifetime greater than or equal to the returned string.
DigestToDependentString(nsTArray<uint8_t> & digest)79 inline nsDependentCSubstring DigestToDependentString(
80     nsTArray<uint8_t>& digest) {
81   return nsDependentCSubstring(BitwiseCast<char*, uint8_t*>(digest.Elements()),
82                                digest.Length());
83 }
84 
85 // Reads a maximum of 8MB from a stream into the supplied buffer.
86 // The reason for the 8MB limit is because this function is used to read
87 // signature-related files and we want to avoid OOM. The uncompressed length of
88 // an entry can be hundreds of times larger than the compressed version,
89 // especially if someone has specifically crafted the entry to cause OOM or to
90 // consume massive amounts of disk space.
91 //
92 // @param stream  The input stream to read from.
93 // @param buf     The buffer that we read the stream into, which must have
94 //                already been allocated.
ReadStream(const nsCOMPtr<nsIInputStream> & stream,SECItem & buf)95 nsresult ReadStream(const nsCOMPtr<nsIInputStream>& stream,
96                     /*out*/ SECItem& buf) {
97   // The size returned by Available() might be inaccurate so we need
98   // to check that Available() matches up with the actual length of
99   // the file.
100   uint64_t length;
101   nsresult rv = stream->Available(&length);
102   if (NS_WARN_IF(NS_FAILED(rv))) {
103     return rv;
104   }
105 
106   // Cap the maximum accepted size of signature-related files at 8MB (which
107   // should be much larger than necessary for our purposes) to avoid OOM.
108   static const uint32_t MAX_LENGTH = 8 * 1000 * 1000;
109   if (length > MAX_LENGTH) {
110     return NS_ERROR_FILE_TOO_BIG;
111   }
112 
113   // With bug 164695 in mind we +1 to leave room for null-terminating
114   // the buffer.
115   SECITEM_AllocItem(buf, static_cast<uint32_t>(length + 1));
116 
117   // buf.len == length + 1. We attempt to read length + 1 bytes
118   // instead of length, so that we can check whether the metadata for
119   // the entry is incorrect.
120   uint32_t bytesRead;
121   rv = stream->Read(BitwiseCast<char*, unsigned char*>(buf.data), buf.len,
122                     &bytesRead);
123   if (NS_WARN_IF(NS_FAILED(rv))) {
124     return rv;
125   }
126   if (bytesRead != length) {
127     return NS_ERROR_FILE_CORRUPTED;
128   }
129 
130   buf.data[buf.len - 1] = 0;  // null-terminate
131 
132   return NS_OK;
133 }
134 
135 // Finds exactly one (signature metadata) JAR entry that matches the given
136 // search pattern, and then loads it. Fails if there are no matches or if
137 // there is more than one match. If bufDigest is not null then on success
138 // bufDigest will contain the digeset of the entry using the given digest
139 // algorithm.
FindAndLoadOneEntry(nsIZipReader * zip,const nsACString & searchPattern,nsACString & filename,SECItem & buf,SECOidTag digestAlgorithm=SEC_OID_SHA1,nsTArray<uint8_t> * bufDigest=nullptr)140 nsresult FindAndLoadOneEntry(
141     nsIZipReader* zip, const nsACString& searchPattern,
142     /*out*/ nsACString& filename,
143     /*out*/ SECItem& buf,
144     /*optional, in*/ SECOidTag digestAlgorithm = SEC_OID_SHA1,
145     /*optional, out*/ nsTArray<uint8_t>* bufDigest = nullptr) {
146   nsCOMPtr<nsIUTF8StringEnumerator> files;
147   nsresult rv = zip->FindEntries(searchPattern, getter_AddRefs(files));
148   if (NS_FAILED(rv) || !files) {
149     return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
150   }
151 
152   bool more;
153   rv = files->HasMore(&more);
154   NS_ENSURE_SUCCESS(rv, rv);
155   if (!more) {
156     return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
157   }
158 
159   rv = files->GetNext(filename);
160   NS_ENSURE_SUCCESS(rv, rv);
161 
162   // Check if there is more than one match, if so then error!
163   rv = files->HasMore(&more);
164   NS_ENSURE_SUCCESS(rv, rv);
165   if (more) {
166     return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
167   }
168 
169   nsCOMPtr<nsIInputStream> stream;
170   rv = zip->GetInputStream(filename, getter_AddRefs(stream));
171   NS_ENSURE_SUCCESS(rv, rv);
172 
173   rv = ReadStream(stream, buf);
174   if (NS_WARN_IF(NS_FAILED(rv))) {
175     return NS_ERROR_SIGNED_JAR_ENTRY_INVALID;
176   }
177 
178   if (bufDigest) {
179     rv = Digest::DigestBuf(digestAlgorithm,
180                            Span<uint8_t>{buf.data, buf.len - 1}, *bufDigest);
181     NS_ENSURE_SUCCESS(rv, rv);
182   }
183 
184   return NS_OK;
185 }
186 
187 // Verify the digest of an entry. We avoid loading the entire entry into memory
188 // at once, which would require memory in proportion to the size of the largest
189 // entry. Instead, we require only a small, fixed amount of memory.
190 //
191 // @param stream  an input stream from a JAR entry or file depending on whether
192 //                it is from a signed archive or unpacked into a directory
193 // @param digestFromManifest The digest that we're supposed to check the file's
194 //                           contents against, from the manifest
195 // @param buf A scratch buffer that we use for doing the I/O, which must have
196 //            already been allocated. The size of this buffer is the unit
197 //            size of our I/O.
VerifyStreamContentDigest(nsIInputStream * stream,const DigestWithAlgorithm & digestFromManifest,SECItem & buf)198 nsresult VerifyStreamContentDigest(
199     nsIInputStream* stream, const DigestWithAlgorithm& digestFromManifest,
200     SECItem& buf) {
201   MOZ_ASSERT(buf.len > 0);
202   nsresult rv = digestFromManifest.ValidateLength();
203   if (NS_FAILED(rv)) {
204     return rv;
205   }
206 
207   uint64_t len64;
208   rv = stream->Available(&len64);
209   NS_ENSURE_SUCCESS(rv, rv);
210   if (len64 > UINT32_MAX) {
211     return NS_ERROR_SIGNED_JAR_ENTRY_TOO_LARGE;
212   }
213 
214   Digest digest;
215 
216   rv = digest.Begin(digestFromManifest.mAlgorithm);
217   NS_ENSURE_SUCCESS(rv, rv);
218 
219   uint64_t totalBytesRead = 0;
220   for (;;) {
221     uint32_t bytesRead;
222     rv = stream->Read(BitwiseCast<char*, unsigned char*>(buf.data), buf.len,
223                       &bytesRead);
224     NS_ENSURE_SUCCESS(rv, rv);
225 
226     if (bytesRead == 0) {
227       break;  // EOF
228     }
229 
230     totalBytesRead += bytesRead;
231     if (totalBytesRead >= UINT32_MAX) {
232       return NS_ERROR_SIGNED_JAR_ENTRY_TOO_LARGE;
233     }
234 
235     rv = digest.Update(buf.data, bytesRead);
236     NS_ENSURE_SUCCESS(rv, rv);
237   }
238 
239   if (totalBytesRead != len64) {
240     // The metadata we used for Available() doesn't match the actual size of
241     // the entry.
242     return NS_ERROR_SIGNED_JAR_ENTRY_INVALID;
243   }
244 
245   // Verify that the digests match.
246   nsTArray<uint8_t> outArray;
247   rv = digest.End(outArray);
248   NS_ENSURE_SUCCESS(rv, rv);
249 
250   nsDependentCSubstring digestStr(DigestToDependentString(outArray));
251   if (!digestStr.Equals(digestFromManifest.mDigest)) {
252     return NS_ERROR_SIGNED_JAR_MODIFIED_ENTRY;
253   }
254 
255   return NS_OK;
256 }
257 
VerifyEntryContentDigest(nsIZipReader * zip,const nsACString & aFilename,const DigestWithAlgorithm & digestFromManifest,SECItem & buf)258 nsresult VerifyEntryContentDigest(nsIZipReader* zip,
259                                   const nsACString& aFilename,
260                                   const DigestWithAlgorithm& digestFromManifest,
261                                   SECItem& buf) {
262   nsCOMPtr<nsIInputStream> stream;
263   nsresult rv = zip->GetInputStream(aFilename, getter_AddRefs(stream));
264   if (NS_FAILED(rv)) {
265     return NS_ERROR_SIGNED_JAR_ENTRY_MISSING;
266   }
267 
268   return VerifyStreamContentDigest(stream, digestFromManifest, buf);
269 }
270 
271 // On input, nextLineStart is the start of the current line. On output,
272 // nextLineStart is the start of the next line.
ReadLine(const char * & nextLineStart,nsCString & line,bool allowContinuations=true)273 nsresult ReadLine(/*in/out*/ const char*& nextLineStart,
274                   /*out*/ nsCString& line, bool allowContinuations = true) {
275   line.Truncate();
276   size_t previousLength = 0;
277   size_t currentLength = 0;
278   for (;;) {
279     const char* eol = strpbrk(nextLineStart, "\r\n");
280 
281     if (!eol) {  // Reached end of file before newline
282       eol = nextLineStart + strlen(nextLineStart);
283     }
284 
285     previousLength = currentLength;
286     line.Append(nextLineStart, eol - nextLineStart);
287     currentLength = line.Length();
288 
289     // The spec says "No line may be longer than 72 bytes (not characters)"
290     // in its UTF8-encoded form.
291     static const size_t lineLimit = 72;
292     if (currentLength - previousLength > lineLimit) {
293       return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
294     }
295 
296     // The spec says: "Implementations should support 65535-byte
297     // (not character) header values..."
298     if (currentLength > 65535) {
299       return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
300     }
301 
302     if (*eol == '\r') {
303       ++eol;
304     }
305     if (*eol == '\n') {
306       ++eol;
307     }
308 
309     nextLineStart = eol;
310 
311     if (*eol != ' ') {
312       // not a continuation
313       return NS_OK;
314     }
315 
316     // continuation
317     if (!allowContinuations) {
318       return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
319     }
320 
321     ++nextLineStart;  // skip space and keep appending
322   }
323 }
324 
325 // The header strings are defined in the JAR specification.
326 #define JAR_MF_SEARCH_STRING "(M|/M)ETA-INF/(M|m)(ANIFEST|anifest).(MF|mf)$"
327 #define JAR_COSE_MF_SEARCH_STRING "(M|/M)ETA-INF/cose.manifest$"
328 #define JAR_SF_SEARCH_STRING "(M|/M)ETA-INF/*.(SF|sf)$"
329 #define JAR_RSA_SEARCH_STRING "(M|/M)ETA-INF/*.(RSA|rsa)$"
330 #define JAR_COSE_SEARCH_STRING "(M|/M)ETA-INF/cose.sig$"
331 #define JAR_META_DIR "META-INF"
332 #define JAR_MF_HEADER "Manifest-Version: 1.0"
333 #define JAR_SF_HEADER "Signature-Version: 1.0"
334 
ParseAttribute(const nsAutoCString & curLine,nsAutoCString & attrName,nsAutoCString & attrValue)335 nsresult ParseAttribute(const nsAutoCString& curLine,
336                         /*out*/ nsAutoCString& attrName,
337                         /*out*/ nsAutoCString& attrValue) {
338   // Find the colon that separates the name from the value.
339   int32_t colonPos = curLine.FindChar(':');
340   if (colonPos == kNotFound) {
341     return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
342   }
343 
344   // set attrName to the name, skipping spaces between the name and colon
345   int32_t nameEnd = colonPos;
346   for (;;) {
347     if (nameEnd == 0) {
348       return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;  // colon with no name
349     }
350     if (curLine[nameEnd - 1] != ' ') break;
351     --nameEnd;
352   }
353   curLine.Left(attrName, nameEnd);
354 
355   // Set attrValue to the value, skipping spaces between the colon and the
356   // value. The value may be empty.
357   int32_t valueStart = colonPos + 1;
358   int32_t curLineLength = curLine.Length();
359   while (valueStart != curLineLength && curLine[valueStart] == ' ') {
360     ++valueStart;
361   }
362   curLine.Right(attrValue, curLineLength - valueStart);
363 
364   return NS_OK;
365 }
366 
367 // Parses the version line of the MF or SF header.
CheckManifestVersion(const char * & nextLineStart,const nsACString & expectedHeader)368 nsresult CheckManifestVersion(const char*& nextLineStart,
369                               const nsACString& expectedHeader) {
370   // The JAR spec says: "Manifest-Version and Signature-Version must be first,
371   // and in exactly that case (so that they can be recognized easily as magic
372   // strings)."
373   nsAutoCString curLine;
374   nsresult rv = ReadLine(nextLineStart, curLine, false);
375   if (NS_FAILED(rv)) {
376     return rv;
377   }
378   if (!curLine.Equals(expectedHeader)) {
379     return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
380   }
381   return NS_OK;
382 }
383 
384 // Parses a signature file (SF) based on the JDK 8 JAR Specification.
385 //
386 // The SF file must contain a SHA*-Digest-Manifest attribute in the main
387 // section (where the * is either 1 or 256, depending on the given digest
388 // algorithm). All other sections are ignored. This means that this will NOT
389 // parse old-style signature files that have separate digests per entry.
390 // The JDK8 x-Digest-Manifest variant is better because:
391 //
392 //   (1) It allows us to follow the principle that we should minimize the
393 //       processing of data that we do before we verify its signature. In
394 //       particular, with the x-Digest-Manifest style, we can verify the digest
395 //       of MANIFEST.MF before we parse it, which prevents malicious JARs
396 //       exploiting our MANIFEST.MF parser.
397 //   (2) It is more time-efficient and space-efficient to have one
398 //       x-Digest-Manifest instead of multiple x-Digest values.
399 //
400 // filebuf must be null-terminated. On output, mfDigest will contain the
401 // decoded value of the appropriate SHA*-DigestManifest, if found.
ParseSF(const char * filebuf,SECOidTag digestAlgorithm,nsAutoCString & mfDigest)402 nsresult ParseSF(const char* filebuf, SECOidTag digestAlgorithm,
403                  /*out*/ nsAutoCString& mfDigest) {
404   const char* digestNameToFind = nullptr;
405   switch (digestAlgorithm) {
406     case SEC_OID_SHA256:
407       digestNameToFind = "sha256-digest-manifest";
408       break;
409     case SEC_OID_SHA1:
410       digestNameToFind = "sha1-digest-manifest";
411       break;
412     default:
413       MOZ_ASSERT_UNREACHABLE("bad argument to ParseSF");
414       return NS_ERROR_FAILURE;
415   }
416 
417   const char* nextLineStart = filebuf;
418   nsresult rv =
419       CheckManifestVersion(nextLineStart, nsLiteralCString(JAR_SF_HEADER));
420   if (NS_FAILED(rv)) {
421     return rv;
422   }
423 
424   for (;;) {
425     nsAutoCString curLine;
426     rv = ReadLine(nextLineStart, curLine);
427     if (NS_FAILED(rv)) {
428       return rv;
429     }
430 
431     if (curLine.Length() == 0) {
432       // End of main section (blank line or end-of-file). We didn't find the
433       // SHA*-Digest-Manifest we were looking for.
434       return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
435     }
436 
437     nsAutoCString attrName;
438     nsAutoCString attrValue;
439     rv = ParseAttribute(curLine, attrName, attrValue);
440     if (NS_FAILED(rv)) {
441       return rv;
442     }
443 
444     if (attrName.EqualsIgnoreCase(digestNameToFind)) {
445       rv = Base64Decode(attrValue, mfDigest);
446       if (NS_FAILED(rv)) {
447         return rv;
448       }
449 
450       // There could be multiple SHA*-Digest-Manifest attributes, which
451       // would be an error, but it's better to just skip any erroneous
452       // duplicate entries rather than trying to detect them, because:
453       //
454       //   (1) It's simpler, and simpler generally means more secure
455       //   (2) An attacker can't make us accept a JAR we would otherwise
456       //       reject just by adding additional SHA*-Digest-Manifest
457       //       attributes.
458       return NS_OK;
459     }
460 
461     // ignore unrecognized attributes
462   }
463 
464   MOZ_ASSERT_UNREACHABLE("somehow exited loop in ParseSF without returning");
465   return NS_ERROR_FAILURE;
466 }
467 
468 // Parses MANIFEST.MF. The filenames of all entries will be returned in
469 // mfItems. buf must be a pre-allocated scratch buffer that is used for doing
470 // I/O. Each file's contents are verified against the entry in the manifest with
471 // the digest algorithm that matches the given one. This algorithm comes from
472 // the signature file. If the signature file has a SHA-256 digest, then SHA-256
473 // entries must be present in the manifest file. If the signature file only has
474 // a SHA-1 digest, then only SHA-1 digests will be used in the manifest file.
ParseMF(const char * filebuf,nsIZipReader * zip,SECOidTag digestAlgorithm,nsTHashtable<nsCStringHashKey> & mfItems,ScopedAutoSECItem & buf)475 nsresult ParseMF(const char* filebuf, nsIZipReader* zip,
476                  SECOidTag digestAlgorithm,
477                  /*out*/ nsTHashtable<nsCStringHashKey>& mfItems,
478                  ScopedAutoSECItem& buf) {
479   const char* digestNameToFind = nullptr;
480   switch (digestAlgorithm) {
481     case SEC_OID_SHA256:
482       digestNameToFind = "sha256-digest";
483       break;
484     case SEC_OID_SHA1:
485       digestNameToFind = "sha1-digest";
486       break;
487     default:
488       MOZ_ASSERT_UNREACHABLE("bad argument to ParseMF");
489       return NS_ERROR_FAILURE;
490   }
491 
492   const char* nextLineStart = filebuf;
493   nsresult rv =
494       CheckManifestVersion(nextLineStart, nsLiteralCString(JAR_MF_HEADER));
495   if (NS_FAILED(rv)) {
496     return rv;
497   }
498 
499   // Skip the rest of the header section, which ends with a blank line.
500   {
501     nsAutoCString line;
502     do {
503       rv = ReadLine(nextLineStart, line);
504       if (NS_FAILED(rv)) {
505         return rv;
506       }
507     } while (line.Length() > 0);
508 
509     // Manifest containing no file entries is OK, though useless.
510     if (*nextLineStart == '\0') {
511       return NS_OK;
512     }
513   }
514 
515   nsAutoCString curItemName;
516   nsAutoCString digest;
517 
518   for (;;) {
519     nsAutoCString curLine;
520     rv = ReadLine(nextLineStart, curLine);
521     if (NS_FAILED(rv)) {
522       return rv;
523     }
524 
525     if (curLine.Length() == 0) {
526       // end of section (blank line or end-of-file)
527 
528       if (curItemName.Length() == 0) {
529         // '...Each section must start with an attribute with the name as
530         // "Name",...', so every section must have a Name attribute.
531         return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
532       }
533 
534       if (digest.IsEmpty()) {
535         // We require every entry to have a digest, since we require every
536         // entry to be signed and we don't allow duplicate entries.
537         return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
538       }
539 
540       if (mfItems.Contains(curItemName)) {
541         // Duplicate entry
542         return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
543       }
544 
545       // Verify that the entry's content digest matches the digest from this
546       // MF section.
547       DigestWithAlgorithm digestWithAlgorithm = {digest, digestAlgorithm};
548       rv = VerifyEntryContentDigest(zip, curItemName, digestWithAlgorithm, buf);
549       if (NS_FAILED(rv)) {
550         return rv;
551       }
552 
553       mfItems.PutEntry(curItemName);
554 
555       if (*nextLineStart == '\0') {
556         // end-of-file
557         break;
558       }
559 
560       // reset so we know we haven't encountered either of these for the next
561       // item yet.
562       curItemName.Truncate();
563       digest.Truncate();
564 
565       continue;  // skip the rest of the loop below
566     }
567 
568     nsAutoCString attrName;
569     nsAutoCString attrValue;
570     rv = ParseAttribute(curLine, attrName, attrValue);
571     if (NS_FAILED(rv)) {
572       return rv;
573     }
574 
575     // Lines to look for:
576 
577     // (1) Digest:
578     if (attrName.EqualsIgnoreCase(digestNameToFind)) {
579       if (!digest.IsEmpty()) {  // multiple SHA* digests in section
580         return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
581       }
582 
583       rv = Base64Decode(attrValue, digest);
584       if (NS_FAILED(rv)) {
585         return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
586       }
587 
588       continue;
589     }
590 
591     // (2) Name: associates this manifest section with a file in the jar.
592     if (attrName.LowerCaseEqualsLiteral("name")) {
593       if (MOZ_UNLIKELY(curItemName.Length() > 0))  // multiple names in section
594         return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
595 
596       if (MOZ_UNLIKELY(attrValue.Length() == 0))
597         return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
598 
599       curItemName = attrValue;
600 
601       continue;
602     }
603 
604     // (3) Magic: the only other must-understand attribute
605     if (attrName.LowerCaseEqualsLiteral("magic")) {
606       // We don't understand any magic, so we can't verify an entry that
607       // requires magic. Since we require every entry to have a valid
608       // signature, we have no choice but to reject the entry.
609       return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
610     }
611 
612     // unrecognized attributes must be ignored
613   }
614 
615   return NS_OK;
616 }
617 
VerifyCertificate(Span<const uint8_t> signerCert,AppTrustedRoot trustedRoot,nsTArray<Span<const uint8_t>> && collectedCerts)618 nsresult VerifyCertificate(Span<const uint8_t> signerCert,
619                            AppTrustedRoot trustedRoot,
620                            nsTArray<Span<const uint8_t>>&& collectedCerts) {
621   AppTrustDomain trustDomain(std::move(collectedCerts));
622   nsresult rv = trustDomain.SetTrustedRoot(trustedRoot);
623   if (NS_FAILED(rv)) {
624     return rv;
625   }
626   Input certDER;
627   mozilla::pkix::Result result =
628       certDER.Init(signerCert.Elements(), signerCert.Length());
629   if (result != Success) {
630     return mozilla::psm::GetXPCOMFromNSSError(MapResultToPRErrorCode(result));
631   }
632 
633   result = BuildCertChain(
634       trustDomain, certDER, Now(), EndEntityOrCA::MustBeEndEntity,
635       KeyUsage::digitalSignature, KeyPurposeId::id_kp_codeSigning,
636       CertPolicyId::anyPolicy, nullptr /*stapledOCSPResponse*/);
637   if (result == mozilla::pkix::Result::ERROR_EXPIRED_CERTIFICATE ||
638       result == mozilla::pkix::Result::ERROR_NOT_YET_VALID_CERTIFICATE) {
639     // For code-signing you normally need trusted 3rd-party timestamps to
640     // handle expiration properly. The signer could always mess with their
641     // system clock so you can't trust the certificate was un-expired when
642     // the signing took place. The choice is either to ignore expiration
643     // or to enforce expiration at time of use. The latter leads to the
644     // user-hostile result that perfectly good code stops working.
645     //
646     // Our package format doesn't support timestamps (nor do we have a
647     // trusted 3rd party timestamper), but since we sign all of our apps and
648     // add-ons ourselves we can trust ourselves not to mess with the clock
649     // on the signing systems. We also have a revocation mechanism if we
650     // need it. Under these conditions it's OK to ignore cert  errors related
651     // to time validity (expiration and "not yet valid").
652     //
653     // This is an invalid approach if
654     //  * we issue certs to let others sign their own packages
655     //  * mozilla::pkix returns "expired" when there are "worse" problems
656     //    with the certificate or chain.
657     // (see bug 1267318)
658     result = Success;
659   }
660   if (result != Success) {
661     return mozilla::psm::GetXPCOMFromNSSError(MapResultToPRErrorCode(result));
662   }
663 
664   return NS_OK;
665 }
666 
667 // Given a SECOidTag representing a digest algorithm (either SEC_OID_SHA1 or
668 // SEC_OID_SHA256), returns the first signerInfo in the given signedData that
669 // purports to have been created using that digest algorithm, or nullptr if
670 // there is none.
671 // The returned signerInfo is owned by signedData, so the caller must ensure
672 // that the lifetime of the signerInfo is contained by the lifetime of the
673 // signedData.
GetSignerInfoForDigestAlgorithm(NSSCMSSignedData * signedData,SECOidTag digestAlgorithm)674 NSSCMSSignerInfo* GetSignerInfoForDigestAlgorithm(NSSCMSSignedData* signedData,
675                                                   SECOidTag digestAlgorithm) {
676   MOZ_ASSERT(digestAlgorithm == SEC_OID_SHA1 ||
677              digestAlgorithm == SEC_OID_SHA256);
678   if (digestAlgorithm != SEC_OID_SHA1 && digestAlgorithm != SEC_OID_SHA256) {
679     return nullptr;
680   }
681 
682   int numSigners = NSS_CMSSignedData_SignerInfoCount(signedData);
683   if (numSigners < 1) {
684     return nullptr;
685   }
686   for (int i = 0; i < numSigners; i++) {
687     NSSCMSSignerInfo* signerInfo =
688         NSS_CMSSignedData_GetSignerInfo(signedData, i);
689     // NSS_CMSSignerInfo_GetDigestAlgTag isn't exported from NSS.
690     SECOidData* digestAlgOID = SECOID_FindOID(&signerInfo->digestAlg.algorithm);
691     if (!digestAlgOID) {
692       continue;
693     }
694     if (digestAlgorithm == digestAlgOID->offset) {
695       return signerInfo;
696     }
697   }
698   return nullptr;
699 }
700 
GetPKCS7SignerCert(NSSCMSSignerInfo * signerInfo,nsTArray<Span<const uint8_t>> & collectedCerts)701 Span<const uint8_t> GetPKCS7SignerCert(
702     NSSCMSSignerInfo* signerInfo,
703     nsTArray<Span<const uint8_t>>& collectedCerts) {
704   if (!signerInfo) {
705     return {};
706   }
707   // The NSS APIs use the term "CMS", but since these are all signed by Mozilla
708   // infrastructure, we know they are actually PKCS7. This means that this only
709   // needs to handle issuer/serial number signer identifiers.
710   if (signerInfo->signerIdentifier.identifierType != NSSCMSSignerID_IssuerSN) {
711     return {};
712   }
713   CERTIssuerAndSN* issuerAndSN = signerInfo->signerIdentifier.id.issuerAndSN;
714   if (!issuerAndSN) {
715     return {};
716   }
717   Input issuer;
718   mozilla::pkix::Result result =
719       issuer.Init(issuerAndSN->derIssuer.data, issuerAndSN->derIssuer.len);
720   if (result != Success) {
721     return {};
722   }
723   Input serialNumber;
724   result = serialNumber.Init(issuerAndSN->serialNumber.data,
725                              issuerAndSN->serialNumber.len);
726   if (result != Success) {
727     return {};
728   }
729   for (const auto& certDER : collectedCerts) {
730     Input certInput;
731     result = certInput.Init(certDER.Elements(), certDER.Length());
732     if (result != Success) {
733       continue;  // probably too big
734     }
735     // Since this only decodes the certificate and doesn't attempt to build a
736     // verified chain with it, the EndEntityOrCA parameter doesn't matter.
737     BackCert cert(certInput, EndEntityOrCA::MustBeEndEntity, nullptr);
738     result = cert.Init();
739     if (result != Success) {
740       continue;
741     }
742     if (InputsAreEqual(issuer, cert.GetIssuer()) &&
743         InputsAreEqual(serialNumber, cert.GetSerialNumber())) {
744       return certDER;
745     }
746   }
747   return {};
748 }
749 
VerifySignature(AppTrustedRoot trustedRoot,const SECItem & buffer,nsTArray<uint8_t> & detachedSHA1Digest,nsTArray<uint8_t> & detachedSHA256Digest,SECOidTag & digestAlgorithm,nsTArray<uint8_t> & signerCert)750 nsresult VerifySignature(AppTrustedRoot trustedRoot, const SECItem& buffer,
751                          nsTArray<uint8_t>& detachedSHA1Digest,
752                          nsTArray<uint8_t>& detachedSHA256Digest,
753                          /*out*/ SECOidTag& digestAlgorithm,
754                          /*out*/ nsTArray<uint8_t>& signerCert) {
755   if (NS_WARN_IF(!buffer.data || buffer.len == 0 ||
756                  detachedSHA1Digest.Length() == 0 ||
757                  detachedSHA256Digest.Length() == 0)) {
758     return NS_ERROR_INVALID_ARG;
759   }
760 
761   UniqueNSSCMSMessage cmsMsg(NSS_CMSMessage_CreateFromDER(
762       const_cast<SECItem*>(&buffer), nullptr, nullptr, nullptr, nullptr,
763       nullptr, nullptr));
764   if (!cmsMsg) {
765     return NS_ERROR_CMS_VERIFY_NOT_SIGNED;
766   }
767 
768   if (!NSS_CMSMessage_IsSigned(cmsMsg.get())) {
769     return NS_ERROR_CMS_VERIFY_NOT_SIGNED;
770   }
771 
772   NSSCMSContentInfo* cinfo = NSS_CMSMessage_ContentLevel(cmsMsg.get(), 0);
773   if (!cinfo) {
774     return NS_ERROR_CMS_VERIFY_NO_CONTENT_INFO;
775   }
776 
777   // We're expecting this to be a PKCS#7 signedData content info.
778   if (NSS_CMSContentInfo_GetContentTypeTag(cinfo) !=
779       SEC_OID_PKCS7_SIGNED_DATA) {
780     return NS_ERROR_CMS_VERIFY_NO_CONTENT_INFO;
781   }
782 
783   // signedData is non-owning
784   NSSCMSSignedData* signedData =
785       static_cast<NSSCMSSignedData*>(NSS_CMSContentInfo_GetContent(cinfo));
786   if (!signedData) {
787     return NS_ERROR_CMS_VERIFY_NO_CONTENT_INFO;
788   }
789 
790   nsTArray<Span<const uint8_t>> collectedCerts;
791   if (signedData->rawCerts) {
792     for (size_t i = 0; signedData->rawCerts[i]; ++i) {
793       Span<const uint8_t> cert(signedData->rawCerts[i]->data,
794                                signedData->rawCerts[i]->len);
795       collectedCerts.AppendElement(std::move(cert));
796     }
797   }
798 
799   NSSCMSSignerInfo* signerInfo =
800       GetSignerInfoForDigestAlgorithm(signedData, SEC_OID_SHA256);
801   nsTArray<uint8_t>* tmpDetachedDigest = &detachedSHA256Digest;
802   digestAlgorithm = SEC_OID_SHA256;
803   if (!signerInfo) {
804     signerInfo = GetSignerInfoForDigestAlgorithm(signedData, SEC_OID_SHA1);
805     if (!signerInfo) {
806       return NS_ERROR_CMS_VERIFY_NOT_SIGNED;
807     }
808     tmpDetachedDigest = &detachedSHA1Digest;
809     digestAlgorithm = SEC_OID_SHA1;
810   }
811 
812   const SECItem detachedDigest = {
813       siBuffer, tmpDetachedDigest->Elements(),
814       static_cast<unsigned int>(tmpDetachedDigest->Length())};
815 
816   // Get the certificate that issued the PKCS7 signature.
817   Span<const uint8_t> signerCertSpan =
818       GetPKCS7SignerCert(signerInfo, collectedCerts);
819   if (signerCertSpan.IsEmpty()) {
820     return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING;
821   }
822 
823   nsresult rv =
824       VerifyCertificate(signerCertSpan, trustedRoot, std::move(collectedCerts));
825   if (NS_FAILED(rv)) {
826     return rv;
827   }
828   signerCert.Clear();
829   signerCert.AppendElements(signerCertSpan);
830 
831   // Ensure that the PKCS#7 data OID is present as the PKCS#9 contentType.
832   const char* pkcs7DataOidString = "1.2.840.113549.1.7.1";
833   ScopedAutoSECItem pkcs7DataOid;
834   if (SEC_StringToOID(nullptr, &pkcs7DataOid, pkcs7DataOidString, 0) !=
835       SECSuccess) {
836     return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING;
837   }
838 
839   // NSS_CMSSignerInfo_Verify relies on NSS_CMSSignerInfo_GetSigningCertificate
840   // having been called already. This relies on the signing certificate being
841   // decoded as a CERTCertificate.
842   // This assertion should never fail, as this certificate has been
843   // successfully verified, which means it fits in the size of an unsigned int.
844   SECItem signingCertificateItem = {
845       siBuffer, const_cast<unsigned char*>(signerCertSpan.Elements()),
846       AssertedCast<unsigned int>(signerCertSpan.Length())};
847   UniqueCERTCertificate signingCertificateHandle(CERT_NewTempCertificate(
848       CERT_GetDefaultCertDB(), &signingCertificateItem, nullptr, false, true));
849   if (!signingCertificateHandle) {
850     return mozilla::psm::GetXPCOMFromNSSError(SEC_ERROR_PKCS7_BAD_SIGNATURE);
851   }
852   // NB: This function does not return an owning reference, unlike with many
853   // other NSS APIs.
854   if (!NSS_CMSSignerInfo_GetSigningCertificate(signerInfo,
855                                                CERT_GetDefaultCertDB())) {
856     return mozilla::psm::GetXPCOMFromNSSError(SEC_ERROR_PKCS7_BAD_SIGNATURE);
857   }
858   return MapSECStatus(NSS_CMSSignerInfo_Verify(
859       signerInfo, const_cast<SECItem*>(&detachedDigest), &pkcs7DataOid));
860 }
861 
862 class CoseVerificationContext {
863  public:
CoseVerificationContext(AppTrustedRoot aTrustedRoot)864   explicit CoseVerificationContext(AppTrustedRoot aTrustedRoot)
865       : mTrustedRoot(aTrustedRoot) {}
866   ~CoseVerificationContext() = default;
867 
GetTrustedRoot()868   AppTrustedRoot GetTrustedRoot() { return mTrustedRoot; }
SetCert(Span<const uint8_t> certDER)869   void SetCert(Span<const uint8_t> certDER) {
870     mCertDER.Clear();
871     mCertDER.AppendElements(certDER);
872   }
873 
TakeCert()874   nsTArray<uint8_t> TakeCert() { return std::move(mCertDER); }
875 
876  private:
877   AppTrustedRoot mTrustedRoot;
878   nsTArray<uint8_t> mCertDER;
879 };
880 
881 // Verification function called from cose-rust.
882 // Returns true if everything goes well and the signature and certificate chain
883 // are good, false in any other case.
CoseVerificationCallback(const uint8_t * aPayload,size_t aPayloadLen,const uint8_t ** aCertChain,size_t aCertChainLen,const size_t * aCertsLen,const uint8_t * aEECert,size_t aEECertLen,const uint8_t * aSignature,size_t aSignatureLen,uint8_t aSignatureAlgorithm,void * ctx)884 bool CoseVerificationCallback(const uint8_t* aPayload, size_t aPayloadLen,
885                               const uint8_t** aCertChain, size_t aCertChainLen,
886                               const size_t* aCertsLen, const uint8_t* aEECert,
887                               size_t aEECertLen, const uint8_t* aSignature,
888                               size_t aSignatureLen, uint8_t aSignatureAlgorithm,
889                               void* ctx) {
890   if (!ctx || !aPayload || !aEECert || !aSignature) {
891     return false;
892   }
893   // The ctx here is a pointer to a CoseVerificationContext object
894   CoseVerificationContext* context = static_cast<CoseVerificationContext*>(ctx);
895   AppTrustedRoot aTrustedRoot = context->GetTrustedRoot();
896 
897   CK_MECHANISM_TYPE mechanism;
898   SECOidTag oid;
899   uint32_t hash_length;
900   SECItem param = {siBuffer, nullptr, 0};
901   switch (aSignatureAlgorithm) {
902     case ES256:
903       mechanism = CKM_ECDSA;
904       oid = SEC_OID_SHA256;
905       hash_length = SHA256_LENGTH;
906       break;
907     case ES384:
908       mechanism = CKM_ECDSA;
909       oid = SEC_OID_SHA384;
910       hash_length = SHA384_LENGTH;
911       break;
912     case ES512:
913       mechanism = CKM_ECDSA;
914       oid = SEC_OID_SHA512;
915       hash_length = SHA512_LENGTH;
916       break;
917     default:
918       return false;
919   }
920 
921   uint8_t hashBuf[HASH_LENGTH_MAX];
922   SECStatus rv = PK11_HashBuf(oid, hashBuf, aPayload, aPayloadLen);
923   if (rv != SECSuccess) {
924     return false;
925   }
926   SECItem hashItem = {siBuffer, hashBuf, hash_length};
927   Input certInput;
928   if (certInput.Init(aEECert, aEECertLen) != Success) {
929     return false;
930   }
931   // Since this only decodes the certificate and doesn't attempt to build a
932   // verified chain with it, the EndEntityOrCA parameter doesn't matter.
933   BackCert backCert(certInput, EndEntityOrCA::MustBeEndEntity, nullptr);
934   if (backCert.Init() != Success) {
935     return false;
936   }
937   Input spkiInput = backCert.GetSubjectPublicKeyInfo();
938   SECItem spkiItem = {siBuffer, const_cast<uint8_t*>(spkiInput.UnsafeGetData()),
939                       spkiInput.GetLength()};
940   UniqueCERTSubjectPublicKeyInfo spki(
941       SECKEY_DecodeDERSubjectPublicKeyInfo(&spkiItem));
942   if (!spki) {
943     return false;
944   }
945   UniqueSECKEYPublicKey key(SECKEY_ExtractPublicKey(spki.get()));
946   SECItem signatureItem = {siBuffer, const_cast<uint8_t*>(aSignature),
947                            static_cast<unsigned int>(aSignatureLen)};
948   rv = PK11_VerifyWithMechanism(key.get(), mechanism, &param, &signatureItem,
949                                 &hashItem, nullptr);
950   if (rv != SECSuccess) {
951     return false;
952   }
953 
954   nsTArray<Span<const uint8_t>> collectedCerts;
955   for (size_t i = 0; i < aCertChainLen; ++i) {
956     Span<const uint8_t> cert(aCertChain[i], aCertsLen[i]);
957     collectedCerts.AppendElement(std::move(cert));
958   }
959 
960   Span<const uint8_t> certSpan = {aEECert, aEECertLen};
961   nsresult nrv =
962       VerifyCertificate(certSpan, aTrustedRoot, std::move(collectedCerts));
963   bool result = true;
964   if (NS_FAILED(nrv)) {
965     result = false;
966   }
967 
968   // Passing back the signing certificate in form of the DER cert.
969   context->SetCert(certSpan);
970   if (NS_FAILED(nrv)) {
971     result = false;
972   }
973 
974   return result;
975 }
976 
VerifyAppManifest(SECOidTag aDigestToUse,nsCOMPtr<nsIZipReader> aZip,nsTHashtable<nsCStringHashKey> & aIgnoredFiles,const SECItem & aManifestBuffer)977 nsresult VerifyAppManifest(SECOidTag aDigestToUse, nsCOMPtr<nsIZipReader> aZip,
978                            nsTHashtable<nsCStringHashKey>& aIgnoredFiles,
979                            const SECItem& aManifestBuffer) {
980   // Allocate the I/O buffer only once per JAR, instead of once per entry, in
981   // order to minimize malloc/free calls and in order to avoid fragmenting
982   // memory.
983   ScopedAutoSECItem buf(128 * 1024);
984 
985   nsTHashtable<nsCStringHashKey> items;
986 
987   nsresult rv =
988       ParseMF(BitwiseCast<char*, unsigned char*>(aManifestBuffer.data), aZip,
989               aDigestToUse, items, buf);
990   if (NS_FAILED(rv)) {
991     return rv;
992   }
993 
994   // Verify every entry in the file.
995   nsCOMPtr<nsIUTF8StringEnumerator> entries;
996   rv = aZip->FindEntries(""_ns, getter_AddRefs(entries));
997   if (NS_FAILED(rv)) {
998     return rv;
999   }
1000   if (!entries) {
1001     return NS_ERROR_UNEXPECTED;
1002   }
1003 
1004   for (;;) {
1005     bool hasMore;
1006     rv = entries->HasMore(&hasMore);
1007     NS_ENSURE_SUCCESS(rv, rv);
1008 
1009     if (!hasMore) {
1010       break;
1011     }
1012 
1013     nsAutoCString entryFilename;
1014     rv = entries->GetNext(entryFilename);
1015     NS_ENSURE_SUCCESS(rv, rv);
1016 
1017     MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
1018             ("Verifying digests for %s", entryFilename.get()));
1019 
1020     if (entryFilename.Length() == 0) {
1021       return NS_ERROR_SIGNED_JAR_ENTRY_INVALID;
1022     }
1023 
1024     // The files that comprise the signature mechanism are not covered by the
1025     // signature. Ignore these files.
1026     if (aIgnoredFiles.Contains(entryFilename)) {
1027       continue;
1028     }
1029 
1030     // Entries with names that end in "/" are directory entries, which are not
1031     // signed.
1032     //
1033     // Since bug 1415991 we don't support unpacked JARs. The "/" entries are
1034     // therefore harmless.
1035     if (entryFilename.Last() == '/') {
1036       continue;
1037     }
1038 
1039     nsCStringHashKey* item = items.GetEntry(entryFilename);
1040     if (!item) {
1041       return NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY;
1042     }
1043 
1044     // Remove the item so we can check for leftover items later
1045     items.RemoveEntry(item);
1046   }
1047 
1048   // We verified that every entry that we require to be signed is signed. But,
1049   // were there any missing entries--that is, entries that are mentioned in the
1050   // manifest but missing from the archive?
1051   if (items.Count() != 0) {
1052     return NS_ERROR_SIGNED_JAR_ENTRY_MISSING;
1053   }
1054 
1055   return NS_OK;
1056 }
1057 
1058 // This corresponds to the preference "security.signed_app_signatures.policy".
1059 // The lowest order bit determines which PKCS#7 algorithms are accepted.
1060 // xxx_0_: SHA-1 and/or SHA-256 PKCS#7 allowed
1061 // xxx_1_: SHA-256 PKCS#7 allowed
1062 // The next two bits determine whether COSE is required and PKCS#7 is allowed
1063 // x_00_x: COSE disabled, ignore files, PKCS#7 must verify
1064 // x_01_x: COSE is verified if present, PKCS#7 must verify
1065 // x_10_x: COSE is required, PKCS#7 must verify if present
1066 // x_11_x: COSE is required, PKCS#7 disabled (fail when present)
1067 class SignaturePolicy {
1068  public:
SignaturePolicy(int32_t preference)1069   explicit SignaturePolicy(int32_t preference)
1070       : mProcessCose(true),
1071         mCoseRequired(false),
1072         mProcessPK7(true),
1073         mPK7Required(true),
1074         mSHA1Allowed(true),
1075         mSHA256Allowed(true) {
1076     mCoseRequired = (preference & 0b100) != 0;
1077     mProcessCose = (preference & 0b110) != 0;
1078     mPK7Required = (preference & 0b100) == 0;
1079     mProcessPK7 = (preference & 0b110) != 0b110;
1080     if ((preference & 0b1) == 0) {
1081       mSHA1Allowed = true;
1082       mSHA256Allowed = true;
1083     } else {
1084       mSHA1Allowed = false;
1085       mSHA256Allowed = true;
1086     }
1087   }
1088   ~SignaturePolicy() = default;
ProcessCOSE()1089   bool ProcessCOSE() { return mProcessCose; }
COSERequired()1090   bool COSERequired() { return mCoseRequired; }
PK7Required()1091   bool PK7Required() { return mPK7Required; }
ProcessPK7()1092   bool ProcessPK7() { return mProcessPK7; }
IsPK7HashAllowed(SECOidTag aHashAlg)1093   bool IsPK7HashAllowed(SECOidTag aHashAlg) {
1094     if (aHashAlg == SEC_OID_SHA256 && mSHA256Allowed) {
1095       return true;
1096     }
1097     if (aHashAlg == SEC_OID_SHA1 && mSHA1Allowed) {
1098       return true;
1099     }
1100     return false;
1101   }
1102 
1103  private:
1104   bool mProcessCose;
1105   bool mCoseRequired;
1106   bool mProcessPK7;
1107   bool mPK7Required;
1108   bool mSHA1Allowed;
1109   bool mSHA256Allowed;
1110 };
1111 
VerifyCOSESignature(AppTrustedRoot aTrustedRoot,nsIZipReader * aZip,SignaturePolicy & aPolicy,nsTHashtable<nsCStringHashKey> & aIgnoredFiles,bool & aVerified,nsTArray<uint8_t> & aCoseCertDER)1112 nsresult VerifyCOSESignature(AppTrustedRoot aTrustedRoot, nsIZipReader* aZip,
1113                              SignaturePolicy& aPolicy,
1114                              nsTHashtable<nsCStringHashKey>& aIgnoredFiles,
1115                              /* out */ bool& aVerified,
1116                              /* out */ nsTArray<uint8_t>& aCoseCertDER) {
1117   NS_ENSURE_ARG_POINTER(aZip);
1118   bool required = aPolicy.COSERequired();
1119   aVerified = false;
1120 
1121   // Read COSE signature file.
1122   nsAutoCString coseFilename;
1123   ScopedAutoSECItem coseBuffer;
1124   nsresult rv = FindAndLoadOneEntry(
1125       aZip, nsLiteralCString(JAR_COSE_SEARCH_STRING), coseFilename, coseBuffer);
1126   if (NS_FAILED(rv)) {
1127     return required ? NS_ERROR_SIGNED_JAR_WRONG_SIGNATURE : NS_OK;
1128   }
1129 
1130   // Verify COSE signature.
1131   nsAutoCString mfFilename;
1132   ScopedAutoSECItem manifestBuffer;
1133   rv = FindAndLoadOneEntry(aZip, nsLiteralCString(JAR_COSE_MF_SEARCH_STRING),
1134                            mfFilename, manifestBuffer);
1135   if (NS_FAILED(rv)) {
1136     return required ? NS_ERROR_SIGNED_JAR_WRONG_SIGNATURE : rv;
1137   }
1138   MOZ_ASSERT(manifestBuffer.len >= 1);
1139   MOZ_ASSERT(coseBuffer.len >= 1);
1140   CoseVerificationContext context(aTrustedRoot);
1141   bool coseVerification = verify_cose_signature_ffi(
1142       manifestBuffer.data, manifestBuffer.len - 1, coseBuffer.data,
1143       coseBuffer.len - 1, &context, CoseVerificationCallback);
1144   if (!coseVerification) {
1145     return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
1146   }
1147   // CoseVerificationCallback sets the context certificate to the first cert
1148   // it encounters.
1149   aCoseCertDER = context.TakeCert();
1150 
1151   // aIgnoredFiles contains the PKCS#7 manifest and signature files iff the
1152   // PKCS#7 verification was successful.
1153   aIgnoredFiles.PutEntry(mfFilename);
1154   aIgnoredFiles.PutEntry(coseFilename);
1155   rv = VerifyAppManifest(SEC_OID_SHA256, aZip, aIgnoredFiles, manifestBuffer);
1156   if (NS_FAILED(rv)) {
1157     return rv;
1158   }
1159 
1160   aVerified = true;
1161   return NS_OK;
1162 }
1163 
VerifyPK7Signature(AppTrustedRoot aTrustedRoot,nsIZipReader * aZip,SignaturePolicy & aPolicy,nsTHashtable<nsCStringHashKey> & aIgnoredFiles,bool & aVerified,nsTArray<uint8_t> & aSignerCert)1164 nsresult VerifyPK7Signature(
1165     AppTrustedRoot aTrustedRoot, nsIZipReader* aZip, SignaturePolicy& aPolicy,
1166     /* out */ nsTHashtable<nsCStringHashKey>& aIgnoredFiles,
1167     /* out */ bool& aVerified,
1168     /* out */ nsTArray<uint8_t>& aSignerCert) {
1169   NS_ENSURE_ARG_POINTER(aZip);
1170   bool required = aPolicy.PK7Required();
1171   aVerified = false;
1172 
1173   // Signature (RSA) file
1174   nsAutoCString sigFilename;
1175   ScopedAutoSECItem sigBuffer;
1176   nsresult rv = FindAndLoadOneEntry(
1177       aZip, nsLiteralCString(JAR_RSA_SEARCH_STRING), sigFilename, sigBuffer);
1178   if (NS_FAILED(rv)) {
1179     return required ? NS_ERROR_SIGNED_JAR_NOT_SIGNED : NS_OK;
1180   }
1181 
1182   // Signature (SF) file
1183   nsAutoCString sfFilename;
1184   ScopedAutoSECItem sfBuffer;
1185   rv = FindAndLoadOneEntry(aZip, nsLiteralCString(JAR_SF_SEARCH_STRING),
1186                            sfFilename, sfBuffer);
1187   if (NS_FAILED(rv)) {
1188     return required ? NS_ERROR_SIGNED_JAR_MANIFEST_INVALID : NS_OK;
1189   }
1190 
1191   // Calculate both the SHA-1 and SHA-256 hashes of the signature file - we
1192   // don't know what algorithm the PKCS#7 signature used.
1193   nsTArray<uint8_t> sfCalculatedSHA1Digest;
1194   rv = Digest::DigestBuf(SEC_OID_SHA1, sfBuffer.data, sfBuffer.len - 1,
1195                          sfCalculatedSHA1Digest);
1196   if (NS_FAILED(rv)) {
1197     return rv;
1198   }
1199 
1200   nsTArray<uint8_t> sfCalculatedSHA256Digest;
1201   rv = Digest::DigestBuf(SEC_OID_SHA256, sfBuffer.data, sfBuffer.len - 1,
1202                          sfCalculatedSHA256Digest);
1203   if (NS_FAILED(rv)) {
1204     return rv;
1205   }
1206 
1207   // Verify PKCS#7 signature.
1208   // If we get here, the signature has to verify even if PKCS#7 is not required.
1209   sigBuffer.type = siBuffer;
1210   SECOidTag digestToUse;
1211   rv = VerifySignature(aTrustedRoot, sigBuffer, sfCalculatedSHA1Digest,
1212                        sfCalculatedSHA256Digest, digestToUse, aSignerCert);
1213   if (NS_FAILED(rv)) {
1214     return rv;
1215   }
1216 
1217   // Check the digest used for the signature against the policy.
1218   if (!aPolicy.IsPK7HashAllowed(digestToUse)) {
1219     return NS_ERROR_SIGNED_JAR_WRONG_SIGNATURE;
1220   }
1221 
1222   nsAutoCString mfDigest;
1223   rv = ParseSF(BitwiseCast<char*, unsigned char*>(sfBuffer.data), digestToUse,
1224                mfDigest);
1225   if (NS_FAILED(rv)) {
1226     return rv;
1227   }
1228 
1229   // Read PK7 manifest (MF) file.
1230   ScopedAutoSECItem manifestBuffer;
1231   nsTArray<uint8_t> digestArray;
1232   nsAutoCString mfFilename;
1233   rv = FindAndLoadOneEntry(aZip, nsLiteralCString(JAR_MF_SEARCH_STRING),
1234                            mfFilename, manifestBuffer, digestToUse,
1235                            &digestArray);
1236   if (NS_FAILED(rv)) {
1237     return rv;
1238   }
1239 
1240   nsDependentCSubstring calculatedDigest(
1241       BitwiseCast<char*, uint8_t*>(digestArray.Elements()),
1242       digestArray.Length());
1243   if (!mfDigest.Equals(calculatedDigest)) {
1244     return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
1245   }
1246 
1247   // Verify PKCS7 manifest file hashes.
1248   aIgnoredFiles.PutEntry(sfFilename);
1249   aIgnoredFiles.PutEntry(sigFilename);
1250   aIgnoredFiles.PutEntry(mfFilename);
1251   rv = VerifyAppManifest(digestToUse, aZip, aIgnoredFiles, manifestBuffer);
1252   if (NS_FAILED(rv)) {
1253     aIgnoredFiles.Clear();
1254     return rv;
1255   }
1256 
1257   aVerified = true;
1258   return NS_OK;
1259 }
1260 
OpenSignedAppFile(AppTrustedRoot aTrustedRoot,nsIFile * aJarFile,SignaturePolicy aPolicy,nsIZipReader ** aZipReader,nsIX509Cert ** aSignerCert)1261 nsresult OpenSignedAppFile(AppTrustedRoot aTrustedRoot, nsIFile* aJarFile,
1262                            SignaturePolicy aPolicy,
1263                            /* out, optional */ nsIZipReader** aZipReader,
1264                            /* out, optional */ nsIX509Cert** aSignerCert) {
1265   NS_ENSURE_ARG_POINTER(aJarFile);
1266 
1267   if (aZipReader) {
1268     *aZipReader = nullptr;
1269   }
1270 
1271   if (aSignerCert) {
1272     *aSignerCert = nullptr;
1273   }
1274 
1275   nsresult rv;
1276 
1277   static NS_DEFINE_CID(kZipReaderCID, NS_ZIPREADER_CID);
1278   nsCOMPtr<nsIZipReader> zip = do_CreateInstance(kZipReaderCID, &rv);
1279   NS_ENSURE_SUCCESS(rv, rv);
1280 
1281   rv = zip->Open(aJarFile);
1282   NS_ENSURE_SUCCESS(rv, rv);
1283 
1284   bool pk7Verified = false;
1285   bool coseVerified = false;
1286   nsTHashtable<nsCStringHashKey> ignoredFiles;
1287   nsTArray<uint8_t> pkcs7CertDER;
1288   nsTArray<uint8_t> coseCertDER;
1289 
1290   // First we have to verify the PKCS#7 signature if there is one.
1291   // This signature covers all files (except for the signature files itself),
1292   // including the COSE signature files. Only when this verification is
1293   // successful the respective files will be ignored in the subsequent COSE
1294   // signature verification.
1295   if (aPolicy.ProcessPK7()) {
1296     rv = VerifyPK7Signature(aTrustedRoot, zip, aPolicy, ignoredFiles,
1297                             pk7Verified, pkcs7CertDER);
1298     if (NS_FAILED(rv)) {
1299       return rv;
1300     }
1301   }
1302 
1303   if (aPolicy.ProcessCOSE()) {
1304     rv = VerifyCOSESignature(aTrustedRoot, zip, aPolicy, ignoredFiles,
1305                              coseVerified, coseCertDER);
1306     if (NS_FAILED(rv)) {
1307       return rv;
1308     }
1309   }
1310 
1311   if ((aPolicy.PK7Required() && !pk7Verified) ||
1312       (aPolicy.COSERequired() && !coseVerified)) {
1313     return NS_ERROR_SIGNED_JAR_WRONG_SIGNATURE;
1314   }
1315 
1316   // Return the reader to the caller if they want it
1317   if (aZipReader) {
1318     zip.forget(aZipReader);
1319   }
1320 
1321   // Return the signer's certificate to the reader if they want it.
1322   if (aSignerCert) {
1323     // The COSE certificate is authoritative.
1324     if (aPolicy.COSERequired() || !coseCertDER.IsEmpty()) {
1325       if (coseCertDER.IsEmpty()) {
1326         return NS_ERROR_FAILURE;
1327       }
1328       nsCOMPtr<nsIX509Cert> signerCert(
1329           new nsNSSCertificate(std::move(coseCertDER)));
1330       signerCert.forget(aSignerCert);
1331     } else {
1332       if (pkcs7CertDER.IsEmpty()) {
1333         return NS_ERROR_FAILURE;
1334       }
1335       nsCOMPtr<nsIX509Cert> signerCert(
1336           new nsNSSCertificate(std::move(pkcs7CertDER)));
1337       signerCert.forget(aSignerCert);
1338     }
1339   }
1340 
1341   return NS_OK;
1342 }
1343 
1344 class OpenSignedAppFileTask final : public CryptoTask {
1345  public:
OpenSignedAppFileTask(AppTrustedRoot aTrustedRoot,nsIFile * aJarFile,SignaturePolicy aPolicy,nsIOpenSignedAppFileCallback * aCallback)1346   OpenSignedAppFileTask(AppTrustedRoot aTrustedRoot, nsIFile* aJarFile,
1347                         SignaturePolicy aPolicy,
1348                         nsIOpenSignedAppFileCallback* aCallback)
1349       : mTrustedRoot(aTrustedRoot),
1350         mJarFile(aJarFile),
1351         mPolicy(aPolicy),
1352         mCallback(new nsMainThreadPtrHolder<nsIOpenSignedAppFileCallback>(
1353             "OpenSignedAppFileTask::mCallback", aCallback)) {}
1354 
1355  private:
CalculateResult()1356   virtual nsresult CalculateResult() override {
1357     return OpenSignedAppFile(mTrustedRoot, mJarFile, mPolicy,
1358                              getter_AddRefs(mZipReader),
1359                              getter_AddRefs(mSignerCert));
1360   }
1361 
CallCallback(nsresult rv)1362   virtual void CallCallback(nsresult rv) override {
1363     (void)mCallback->OpenSignedAppFileFinished(rv, mZipReader, mSignerCert);
1364   }
1365 
1366   const AppTrustedRoot mTrustedRoot;
1367   const nsCOMPtr<nsIFile> mJarFile;
1368   const SignaturePolicy mPolicy;
1369   nsMainThreadPtrHandle<nsIOpenSignedAppFileCallback> mCallback;
1370   nsCOMPtr<nsIZipReader> mZipReader;  // out
1371   nsCOMPtr<nsIX509Cert> mSignerCert;  // out
1372 };
1373 
1374 static const int32_t sDefaultSignaturePolicy = 0b10;
1375 
1376 }  // unnamed namespace
1377 
1378 NS_IMETHODIMP
OpenSignedAppFileAsync(AppTrustedRoot aTrustedRoot,nsIFile * aJarFile,nsIOpenSignedAppFileCallback * aCallback)1379 nsNSSCertificateDB::OpenSignedAppFileAsync(
1380     AppTrustedRoot aTrustedRoot, nsIFile* aJarFile,
1381     nsIOpenSignedAppFileCallback* aCallback) {
1382   NS_ENSURE_ARG_POINTER(aJarFile);
1383   NS_ENSURE_ARG_POINTER(aCallback);
1384   if (!NS_IsMainThread()) {
1385     return NS_ERROR_NOT_SAME_THREAD;
1386   }
1387   int32_t policyInt =
1388       Preferences::GetInt("security.signed_app_signatures.policy",
1389                           static_cast<int32_t>(sDefaultSignaturePolicy));
1390   SignaturePolicy policy(policyInt);
1391   RefPtr<OpenSignedAppFileTask> task(
1392       new OpenSignedAppFileTask(aTrustedRoot, aJarFile, policy, aCallback));
1393   return task->Dispatch();
1394 }
1395