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, ¶m, &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