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