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