Bug 1928997: Update tabs icon in Unified Search popup r=desktop-theme-reviewers,daleh...
[gecko.git] / security / manager / ssl / AppSignatureVerification.cpp
blob991006e1c1e628b78ccf0081cc37050079e81eaf
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/. */
7 #include "nsNSSCertificateDB.h"
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"
43 using namespace mozilla::pkix;
44 using namespace mozilla;
45 using namespace mozilla::psm;
47 extern mozilla::LazyLogModule gPIPNSSLog;
49 namespace {
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 {
54 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;
68 if (mDigest.Length() != hashLen) {
69 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
71 return NS_OK;
74 nsAutoCString mDigest;
75 SECOidTag mAlgorithm;
78 // The digest must have a lifetime greater than or equal to the returned string.
79 inline nsDependentCSubstring DigestToDependentString(
80 nsTArray<uint8_t>& digest) {
81 return nsDependentCSubstring(BitwiseCast<char*, uint8_t*>(digest.Elements()),
82 digest.Length());
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.
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.
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;
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;
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));
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;
126 if (bytesRead != length) {
127 return NS_ERROR_FILE_CORRUPTED;
130 buf.data[buf.len - 1] = 0; // null-terminate
132 return NS_OK;
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.
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;
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;
159 rv = files->GetNext(filename);
160 NS_ENSURE_SUCCESS(rv, rv);
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;
169 nsCOMPtr<nsIInputStream> stream;
170 rv = zip->GetInputStream(filename, getter_AddRefs(stream));
171 NS_ENSURE_SUCCESS(rv, rv);
173 rv = ReadStream(stream, buf);
174 if (NS_WARN_IF(NS_FAILED(rv))) {
175 return NS_ERROR_SIGNED_JAR_ENTRY_INVALID;
178 if (bufDigest) {
179 rv = Digest::DigestBuf(digestAlgorithm,
180 Span<uint8_t>{buf.data, buf.len - 1}, *bufDigest);
181 NS_ENSURE_SUCCESS(rv, rv);
184 return NS_OK;
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.
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.
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;
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;
214 Digest digest;
216 rv = digest.Begin(digestFromManifest.mAlgorithm);
217 NS_ENSURE_SUCCESS(rv, rv);
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);
226 if (bytesRead == 0) {
227 break; // EOF
230 totalBytesRead += bytesRead;
231 if (totalBytesRead >= UINT32_MAX) {
232 return NS_ERROR_SIGNED_JAR_ENTRY_TOO_LARGE;
235 rv = digest.Update(buf.data, bytesRead);
236 NS_ENSURE_SUCCESS(rv, rv);
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;
245 // Verify that the digests match.
246 nsTArray<uint8_t> outArray;
247 rv = digest.End(outArray);
248 NS_ENSURE_SUCCESS(rv, rv);
250 nsDependentCSubstring digestStr(DigestToDependentString(outArray));
251 if (!digestStr.Equals(digestFromManifest.mDigest)) {
252 return NS_ERROR_SIGNED_JAR_MODIFIED_ENTRY;
255 return NS_OK;
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;
268 return VerifyStreamContentDigest(stream, digestFromManifest, buf);
271 // On input, nextLineStart is the start of the current line. On output,
272 // nextLineStart is the start of the next line.
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");
281 if (!eol) { // Reached end of file before newline
282 eol = nextLineStart + strlen(nextLineStart);
285 previousLength = currentLength;
286 line.Append(nextLineStart, eol - nextLineStart);
287 currentLength = line.Length();
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;
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;
302 if (*eol == '\r') {
303 ++eol;
305 if (*eol == '\n') {
306 ++eol;
309 nextLineStart = eol;
311 if (*eol != ' ') {
312 // not a continuation
313 return NS_OK;
316 // continuation
317 if (!allowContinuations) {
318 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
321 ++nextLineStart; // skip space and keep appending
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"
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;
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
350 if (curLine[nameEnd - 1] != ' ') break;
351 --nameEnd;
353 curLine.Left(attrName, nameEnd);
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;
362 curLine.Right(attrValue, curLineLength - valueStart);
364 return NS_OK;
367 // Parses the version line of the MF or SF header.
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;
378 if (!curLine.Equals(expectedHeader)) {
379 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
381 return NS_OK;
384 // Parses a signature file (SF) based on the JDK 8 JAR Specification.
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:
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.
400 // filebuf must be null-terminated. On output, mfDigest will contain the
401 // decoded value of the appropriate SHA*-DigestManifest, if found.
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;
417 const char* nextLineStart = filebuf;
418 nsresult rv =
419 CheckManifestVersion(nextLineStart, nsLiteralCString(JAR_SF_HEADER));
420 if (NS_FAILED(rv)) {
421 return rv;
424 for (;;) {
425 nsAutoCString curLine;
426 rv = ReadLine(nextLineStart, curLine);
427 if (NS_FAILED(rv)) {
428 return rv;
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;
437 nsAutoCString attrName;
438 nsAutoCString attrValue;
439 rv = ParseAttribute(curLine, attrName, attrValue);
440 if (NS_FAILED(rv)) {
441 return rv;
444 if (attrName.EqualsIgnoreCase(digestNameToFind)) {
445 rv = Base64Decode(attrValue, mfDigest);
446 if (NS_FAILED(rv)) {
447 return rv;
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:
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;
461 // ignore unrecognized attributes
464 MOZ_ASSERT_UNREACHABLE("somehow exited loop in ParseSF without returning");
465 return NS_ERROR_FAILURE;
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.
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;
492 const char* nextLineStart = filebuf;
493 nsresult rv =
494 CheckManifestVersion(nextLineStart, nsLiteralCString(JAR_MF_HEADER));
495 if (NS_FAILED(rv)) {
496 return rv;
499 // Skip the rest of the header section, which ends with a blank line.
501 nsAutoCString line;
502 do {
503 rv = ReadLine(nextLineStart, line);
504 if (NS_FAILED(rv)) {
505 return rv;
507 } while (line.Length() > 0);
509 // Manifest containing no file entries is OK, though useless.
510 if (*nextLineStart == '\0') {
511 return NS_OK;
515 nsAutoCString curItemName;
516 nsAutoCString digest;
518 for (;;) {
519 nsAutoCString curLine;
520 rv = ReadLine(nextLineStart, curLine);
521 if (NS_FAILED(rv)) {
522 return rv;
525 if (curLine.Length() == 0) {
526 // end of section (blank line or end-of-file)
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;
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;
540 if (mfItems.Contains(curItemName)) {
541 // Duplicate entry
542 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
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;
553 mfItems.PutEntry(curItemName);
555 if (*nextLineStart == '\0') {
556 // end-of-file
557 break;
560 // reset so we know we haven't encountered either of these for the next
561 // item yet.
562 curItemName.Truncate();
563 digest.Truncate();
565 continue; // skip the rest of the loop below
568 nsAutoCString attrName;
569 nsAutoCString attrValue;
570 rv = ParseAttribute(curLine, attrName, attrValue);
571 if (NS_FAILED(rv)) {
572 return rv;
575 // Lines to look for:
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;
583 rv = Base64Decode(attrValue, digest);
584 if (NS_FAILED(rv)) {
585 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
588 continue;
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;
596 if (MOZ_UNLIKELY(attrValue.Length() == 0))
597 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
599 curItemName = attrValue;
601 continue;
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;
612 // unrecognized attributes must be ignored
615 return NS_OK;
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;
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));
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.
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").
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;
660 if (result != Success) {
661 return mozilla::psm::GetXPCOMFromNSSError(MapResultToPRErrorCode(result));
664 return NS_OK;
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.
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;
682 int numSigners = NSS_CMSSignedData_SignerInfoCount(signedData);
683 if (numSigners < 1) {
684 return nullptr;
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;
694 if (digestAlgorithm == digestAlgOID->offset) {
695 return signerInfo;
698 return nullptr;
701 Span<const uint8_t> GetPKCS7SignerCert(
702 NSSCMSSignerInfo* signerInfo,
703 nsTArray<Span<const uint8_t>>& collectedCerts) {
704 if (!signerInfo) {
705 return {};
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 {};
713 CERTIssuerAndSN* issuerAndSN = signerInfo->signerIdentifier.id.issuerAndSN;
714 if (!issuerAndSN) {
715 return {};
717 Input issuer;
718 mozilla::pkix::Result result =
719 issuer.Init(issuerAndSN->derIssuer.data, issuerAndSN->derIssuer.len);
720 if (result != Success) {
721 return {};
723 Input serialNumber;
724 result = serialNumber.Init(issuerAndSN->serialNumber.data,
725 issuerAndSN->serialNumber.len);
726 if (result != Success) {
727 return {};
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
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;
742 if (InputsAreEqual(issuer, cert.GetIssuer()) &&
743 InputsAreEqual(serialNumber, cert.GetSerialNumber())) {
744 return certDER;
747 return {};
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;
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;
768 if (!NSS_CMSMessage_IsSigned(cmsMsg.get())) {
769 return NS_ERROR_CMS_VERIFY_NOT_SIGNED;
772 NSSCMSContentInfo* cinfo = NSS_CMSMessage_ContentLevel(cmsMsg.get(), 0);
773 if (!cinfo) {
774 return NS_ERROR_CMS_VERIFY_NO_CONTENT_INFO;
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;
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;
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));
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;
808 tmpDetachedDigest = &detachedSHA1Digest;
809 digestAlgorithm = SEC_OID_SHA1;
812 const SECItem detachedDigest = {
813 siBuffer, tmpDetachedDigest->Elements(),
814 static_cast<unsigned int>(tmpDetachedDigest->Length())};
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;
823 nsresult rv =
824 VerifyCertificate(signerCertSpan, trustedRoot, std::move(collectedCerts));
825 if (NS_FAILED(rv)) {
826 return rv;
828 signerCert.Clear();
829 signerCert.AppendElements(signerCertSpan);
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;
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);
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);
858 return MapSECStatus(NSS_CMSSignerInfo_Verify(
859 signerInfo, const_cast<SECItem*>(&detachedDigest), &pkcs7DataOid));
862 class CoseVerificationContext {
863 public:
864 explicit CoseVerificationContext(AppTrustedRoot aTrustedRoot)
865 : mTrustedRoot(aTrustedRoot) {}
866 ~CoseVerificationContext() = default;
868 AppTrustedRoot GetTrustedRoot() { return mTrustedRoot; }
869 void SetCert(Span<const uint8_t> certDER) {
870 mCertDER.Clear();
871 mCertDER.AppendElements(certDER);
874 nsTArray<uint8_t> TakeCert() { return std::move(mCertDER); }
876 private:
877 AppTrustedRoot mTrustedRoot;
878 nsTArray<uint8_t> mCertDER;
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.
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;
893 // The ctx here is a pointer to a CoseVerificationContext object
894 CoseVerificationContext* context = static_cast<CoseVerificationContext*>(ctx);
895 AppTrustedRoot aTrustedRoot = context->GetTrustedRoot();
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;
921 uint8_t hashBuf[HASH_LENGTH_MAX];
922 SECStatus rv = PK11_HashBuf(oid, hashBuf, aPayload, aPayloadLen);
923 if (rv != SECSuccess) {
924 return false;
926 SECItem hashItem = {siBuffer, hashBuf, hash_length};
927 Input certInput;
928 if (certInput.Init(aEECert, aEECertLen) != Success) {
929 return false;
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;
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;
945 UniqueSECKEYPublicKey key(SECKEY_ExtractPublicKey(spki.get()));
946 SECItem signatureItem = {siBuffer, const_cast<uint8_t*>(aSignature),
947 static_cast<unsigned int>(aSignatureLen)};
948 rv = PK11_VerifyWithMechanism(key.get(), mechanism, &param, &signatureItem,
949 &hashItem, nullptr);
950 if (rv != SECSuccess) {
951 return false;
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));
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;
968 // Passing back the signing certificate in form of the DER cert.
969 context->SetCert(certSpan);
970 if (NS_FAILED(nrv)) {
971 result = false;
974 return result;
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);
985 nsTHashtable<nsCStringHashKey> items;
987 nsresult rv =
988 ParseMF(BitwiseCast<char*, unsigned char*>(aManifestBuffer.data), aZip,
989 aDigestToUse, items, buf);
990 if (NS_FAILED(rv)) {
991 return rv;
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;
1000 if (!entries) {
1001 return NS_ERROR_UNEXPECTED;
1004 for (;;) {
1005 bool hasMore;
1006 rv = entries->HasMore(&hasMore);
1007 NS_ENSURE_SUCCESS(rv, rv);
1009 if (!hasMore) {
1010 break;
1013 nsAutoCString entryFilename;
1014 rv = entries->GetNext(entryFilename);
1015 NS_ENSURE_SUCCESS(rv, rv);
1017 MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
1018 ("Verifying digests for %s", entryFilename.get()));
1020 if (entryFilename.Length() == 0) {
1021 return NS_ERROR_SIGNED_JAR_ENTRY_INVALID;
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;
1030 // Entries with names that end in "/" are directory entries, which are not
1031 // signed.
1033 // Since bug 1415991 we don't support unpacked JARs. The "/" entries are
1034 // therefore harmless.
1035 if (entryFilename.Last() == '/') {
1036 continue;
1039 nsCStringHashKey* item = items.GetEntry(entryFilename);
1040 if (!item) {
1041 return NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY;
1044 // Remove the item so we can check for leftover items later
1045 items.RemoveEntry(item);
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;
1055 return NS_OK;
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:
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;
1088 ~SignaturePolicy() = default;
1089 bool ProcessCOSE() { return mProcessCose; }
1090 bool COSERequired() { return mCoseRequired; }
1091 bool PK7Required() { return mPK7Required; }
1092 bool ProcessPK7() { return mProcessPK7; }
1093 bool IsPK7HashAllowed(SECOidTag aHashAlg) {
1094 if (aHashAlg == SEC_OID_SHA256 && mSHA256Allowed) {
1095 return true;
1097 if (aHashAlg == SEC_OID_SHA1 && mSHA1Allowed) {
1098 return true;
1100 return false;
1103 private:
1104 bool mProcessCose;
1105 bool mCoseRequired;
1106 bool mProcessPK7;
1107 bool mPK7Required;
1108 bool mSHA1Allowed;
1109 bool mSHA256Allowed;
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;
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;
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;
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;
1147 // CoseVerificationCallback sets the context certificate to the first cert
1148 // it encounters.
1149 aCoseCertDER = context.TakeCert();
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;
1160 aVerified = true;
1161 return NS_OK;
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 /* out */ SECOidTag& aHashAlgorithm) {
1170 NS_ENSURE_ARG_POINTER(aZip);
1171 bool required = aPolicy.PK7Required();
1172 aVerified = false;
1174 // Signature (RSA) file
1175 nsAutoCString sigFilename;
1176 ScopedAutoSECItem sigBuffer;
1177 nsresult rv = FindAndLoadOneEntry(
1178 aZip, nsLiteralCString(JAR_RSA_SEARCH_STRING), sigFilename, sigBuffer);
1179 if (NS_FAILED(rv)) {
1180 return required ? NS_ERROR_SIGNED_JAR_NOT_SIGNED : NS_OK;
1183 // Signature (SF) file
1184 nsAutoCString sfFilename;
1185 ScopedAutoSECItem sfBuffer;
1186 rv = FindAndLoadOneEntry(aZip, nsLiteralCString(JAR_SF_SEARCH_STRING),
1187 sfFilename, sfBuffer);
1188 if (NS_FAILED(rv)) {
1189 return required ? NS_ERROR_SIGNED_JAR_MANIFEST_INVALID : NS_OK;
1192 // Calculate both the SHA-1 and SHA-256 hashes of the signature file - we
1193 // don't know what algorithm the PKCS#7 signature used.
1194 nsTArray<uint8_t> sfCalculatedSHA1Digest;
1195 rv = Digest::DigestBuf(SEC_OID_SHA1, sfBuffer.data, sfBuffer.len - 1,
1196 sfCalculatedSHA1Digest);
1197 if (NS_FAILED(rv)) {
1198 return rv;
1201 nsTArray<uint8_t> sfCalculatedSHA256Digest;
1202 rv = Digest::DigestBuf(SEC_OID_SHA256, sfBuffer.data, sfBuffer.len - 1,
1203 sfCalculatedSHA256Digest);
1204 if (NS_FAILED(rv)) {
1205 return rv;
1208 // Verify PKCS#7 signature.
1209 // If we get here, the signature has to verify even if PKCS#7 is not required.
1210 sigBuffer.type = siBuffer;
1211 SECOidTag digestToUse;
1212 rv = VerifySignature(aTrustedRoot, sigBuffer, sfCalculatedSHA1Digest,
1213 sfCalculatedSHA256Digest, digestToUse, aSignerCert);
1214 if (NS_FAILED(rv)) {
1215 return rv;
1218 // Check the digest used for the signature against the policy.
1219 if (!aPolicy.IsPK7HashAllowed(digestToUse)) {
1220 return NS_ERROR_SIGNED_JAR_WRONG_SIGNATURE;
1223 nsAutoCString mfDigest;
1224 rv = ParseSF(BitwiseCast<char*, unsigned char*>(sfBuffer.data), digestToUse,
1225 mfDigest);
1226 if (NS_FAILED(rv)) {
1227 return rv;
1230 // Read PK7 manifest (MF) file.
1231 ScopedAutoSECItem manifestBuffer;
1232 nsTArray<uint8_t> digestArray;
1233 nsAutoCString mfFilename;
1234 rv = FindAndLoadOneEntry(aZip, nsLiteralCString(JAR_MF_SEARCH_STRING),
1235 mfFilename, manifestBuffer, digestToUse,
1236 &digestArray);
1237 if (NS_FAILED(rv)) {
1238 return rv;
1241 nsDependentCSubstring calculatedDigest(
1242 BitwiseCast<char*, uint8_t*>(digestArray.Elements()),
1243 digestArray.Length());
1244 if (!mfDigest.Equals(calculatedDigest)) {
1245 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
1248 // Verify PKCS7 manifest file hashes.
1249 aIgnoredFiles.PutEntry(sfFilename);
1250 aIgnoredFiles.PutEntry(sigFilename);
1251 aIgnoredFiles.PutEntry(mfFilename);
1252 rv = VerifyAppManifest(digestToUse, aZip, aIgnoredFiles, manifestBuffer);
1253 if (NS_FAILED(rv)) {
1254 aIgnoredFiles.Clear();
1255 return rv;
1258 aVerified = true;
1259 aHashAlgorithm = digestToUse;
1260 return NS_OK;
1263 class AppSignatureInfo final : public nsIAppSignatureInfo {
1264 public:
1265 NS_DECL_THREADSAFE_ISUPPORTS
1267 AppSignatureInfo(RefPtr<nsIX509Cert>&& signerCert,
1268 nsIAppSignatureInfo::SignatureAlgorithm signatureAlgorithm)
1269 : mSignerCert(std::move(signerCert)),
1270 mSignatureAlgorithm(signatureAlgorithm) {}
1272 NS_IMETHODIMP GetSignerCert(nsIX509Cert** signerCert) override {
1273 *signerCert = do_AddRef(mSignerCert).take();
1274 return NS_OK;
1277 NS_IMETHODIMP GetSignatureAlgorithm(
1278 nsIAppSignatureInfo::SignatureAlgorithm* signatureAlgorithm) override {
1279 *signatureAlgorithm = mSignatureAlgorithm;
1280 return NS_OK;
1283 private:
1284 ~AppSignatureInfo() = default;
1286 RefPtr<nsIX509Cert> mSignerCert;
1287 nsIAppSignatureInfo::SignatureAlgorithm mSignatureAlgorithm;
1290 NS_IMPL_ISUPPORTS(AppSignatureInfo, nsIAppSignatureInfo)
1292 nsresult OpenSignedAppFile(
1293 AppTrustedRoot aTrustedRoot, nsIFile* aJarFile, SignaturePolicy aPolicy,
1294 /* out */ nsIZipReader** aZipReader,
1295 /* out */ nsTArray<RefPtr<nsIAppSignatureInfo>>& aSignatureInfos) {
1296 NS_ENSURE_ARG_POINTER(aJarFile);
1298 if (aZipReader) {
1299 *aZipReader = nullptr;
1302 aSignatureInfos.Clear();
1304 nsresult rv;
1306 static NS_DEFINE_CID(kZipReaderCID, NS_ZIPREADER_CID);
1307 nsCOMPtr<nsIZipReader> zip = do_CreateInstance(kZipReaderCID, &rv);
1308 NS_ENSURE_SUCCESS(rv, rv);
1310 rv = zip->Open(aJarFile);
1311 NS_ENSURE_SUCCESS(rv, rv);
1313 nsTHashtable<nsCStringHashKey> ignoredFiles;
1314 bool pk7Verified = false;
1315 nsTArray<uint8_t> pkcs7CertDER;
1316 SECOidTag pkcs7HashAlgorithm = SEC_OID_UNKNOWN;
1317 bool coseVerified = false;
1318 nsTArray<uint8_t> coseCertDER;
1320 // First we have to verify the PKCS#7 signature if there is one.
1321 // This signature covers all files (except for the signature files itself),
1322 // including the COSE signature files. Only when this verification is
1323 // successful the respective files will be ignored in the subsequent COSE
1324 // signature verification.
1325 if (aPolicy.ProcessPK7()) {
1326 rv = VerifyPK7Signature(aTrustedRoot, zip, aPolicy, ignoredFiles,
1327 pk7Verified, pkcs7CertDER, pkcs7HashAlgorithm);
1328 if (NS_FAILED(rv)) {
1329 return rv;
1333 if (aPolicy.ProcessCOSE()) {
1334 rv = VerifyCOSESignature(aTrustedRoot, zip, aPolicy, ignoredFiles,
1335 coseVerified, coseCertDER);
1336 if (NS_FAILED(rv)) {
1337 return rv;
1341 // Bits 1 and 2
1342 // 00 = Didn't Process PKCS#7 signatures
1343 // 01 = Processed but no valid cert or signature
1344 // 10 = Processed and valid cert found, but addon didn't match manifest
1345 // 11 = Processed and valid.
1346 // Bits 3 and 4 are the same but for COSE.
1347 uint32_t bucket = 0;
1348 bucket += aPolicy.ProcessCOSE();
1349 bucket += !coseCertDER.IsEmpty();
1350 bucket += coseVerified;
1351 bucket <<= 2;
1352 bucket += aPolicy.ProcessPK7();
1353 bucket += !pkcs7CertDER.IsEmpty();
1354 bucket += pk7Verified;
1355 Telemetry::Accumulate(Telemetry::ADDON_SIGNATURE_VERIFICATION_STATUS, bucket);
1357 if ((aPolicy.PK7Required() && !pk7Verified) ||
1358 (aPolicy.COSERequired() && !coseVerified)) {
1359 return NS_ERROR_SIGNED_JAR_WRONG_SIGNATURE;
1362 // Return the reader to the caller if they want it
1363 if (aZipReader) {
1364 zip.forget(aZipReader);
1367 // Return the signature information (a list of signing certificate and
1368 // algorithm pairs). If present, the COSE signature will be first, followed
1369 // by any PKCS7 signatures.
1370 if (coseVerified && !coseCertDER.IsEmpty()) {
1371 RefPtr<nsIX509Cert> signerCert(
1372 new nsNSSCertificate(std::move(coseCertDER)));
1373 aSignatureInfos.AppendElement(new AppSignatureInfo(
1374 std::move(signerCert),
1375 nsIAppSignatureInfo::SignatureAlgorithm::COSE_WITH_SHA256));
1377 if (pk7Verified && !pkcs7CertDER.IsEmpty()) {
1378 RefPtr<nsIX509Cert> signerCert(
1379 new nsNSSCertificate(std::move(pkcs7CertDER)));
1380 nsIAppSignatureInfo::SignatureAlgorithm signatureAlgorithm;
1381 switch (pkcs7HashAlgorithm) {
1382 case SEC_OID_SHA1:
1383 signatureAlgorithm =
1384 nsIAppSignatureInfo::SignatureAlgorithm::PKCS7_WITH_SHA1;
1385 break;
1386 case SEC_OID_SHA256:
1387 signatureAlgorithm =
1388 nsIAppSignatureInfo::SignatureAlgorithm::PKCS7_WITH_SHA256;
1389 break;
1390 default:
1391 return NS_ERROR_FAILURE;
1393 aSignatureInfos.AppendElement(
1394 new AppSignatureInfo(std::move(signerCert), signatureAlgorithm));
1397 return NS_OK;
1400 class OpenSignedAppFileTask final : public CryptoTask {
1401 public:
1402 OpenSignedAppFileTask(AppTrustedRoot aTrustedRoot, nsIFile* aJarFile,
1403 SignaturePolicy aPolicy,
1404 nsIOpenSignedAppFileCallback* aCallback)
1405 : mTrustedRoot(aTrustedRoot),
1406 mJarFile(aJarFile),
1407 mPolicy(aPolicy),
1408 mCallback(new nsMainThreadPtrHolder<nsIOpenSignedAppFileCallback>(
1409 "OpenSignedAppFileTask::mCallback", aCallback)) {}
1411 private:
1412 virtual nsresult CalculateResult() override {
1413 return OpenSignedAppFile(mTrustedRoot, mJarFile, mPolicy,
1414 getter_AddRefs(mZipReader), mSignatureInfos);
1417 virtual void CallCallback(nsresult rv) override {
1418 (void)mCallback->OpenSignedAppFileFinished(rv, mZipReader, mSignatureInfos);
1421 const AppTrustedRoot mTrustedRoot;
1422 const nsCOMPtr<nsIFile> mJarFile;
1423 const SignaturePolicy mPolicy;
1424 nsMainThreadPtrHandle<nsIOpenSignedAppFileCallback> mCallback;
1425 nsCOMPtr<nsIZipReader> mZipReader; // out
1426 nsTArray<RefPtr<nsIAppSignatureInfo>> mSignatureInfos; // out
1429 static const int32_t sDefaultSignaturePolicy = 0b10;
1431 } // unnamed namespace
1433 NS_IMETHODIMP
1434 nsNSSCertificateDB::OpenSignedAppFileAsync(
1435 AppTrustedRoot aTrustedRoot, nsIFile* aJarFile,
1436 nsIOpenSignedAppFileCallback* aCallback) {
1437 NS_ENSURE_ARG_POINTER(aJarFile);
1438 NS_ENSURE_ARG_POINTER(aCallback);
1439 if (!NS_IsMainThread()) {
1440 return NS_ERROR_NOT_SAME_THREAD;
1442 int32_t policyInt =
1443 Preferences::GetInt("security.signed_app_signatures.policy",
1444 static_cast<int32_t>(sDefaultSignaturePolicy));
1445 SignaturePolicy policy(policyInt);
1446 RefPtr<OpenSignedAppFileTask> task(
1447 new OpenSignedAppFileTask(aTrustedRoot, aJarFile, policy, aCallback));
1448 return task->Dispatch();