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"
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"
25 #include "nsComponentManagerUtils.h"
26 #include "nsDependentString.h"
27 #include "nsHashKeys.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"
36 #include "nsTHashtable.h"
37 #include "mozpkix/pkix.h"
38 #include "mozpkix/pkixnss.h"
39 #include "mozpkix/pkixutil.h"
43 using namespace mozilla::pkix
;
44 using namespace mozilla
;
45 using namespace mozilla::psm
;
47 extern mozilla::LazyLogModule gPIPNSSLog
;
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 {
58 hashLen
= SHA256_LENGTH
;
61 hashLen
= SHA1_LENGTH
;
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
;
74 nsAutoCString mDigest
;
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()),
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
101 nsresult rv
= stream
->Available(&length
);
102 if (NS_WARN_IF(NS_FAILED(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
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.
121 rv
= stream
->Read(BitwiseCast
<char*, unsigned char*>(buf
.data
), buf
.len
,
123 if (NS_WARN_IF(NS_FAILED(rv
))) {
126 if (bytesRead
!= length
) {
127 return NS_ERROR_FILE_CORRUPTED
;
130 buf
.data
[buf
.len
- 1] = 0; // null-terminate
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
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
;
153 rv
= files
->HasMore(&more
);
154 NS_ENSURE_SUCCESS(rv
, rv
);
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
);
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
;
179 rv
= Digest::DigestBuf(digestAlgorithm
,
180 Span
<uint8_t>{buf
.data
, buf
.len
- 1}, *bufDigest
);
181 NS_ENSURE_SUCCESS(rv
, rv
);
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
198 nsresult
VerifyStreamContentDigest(
199 nsIInputStream
* stream
, const DigestWithAlgorithm
& digestFromManifest
,
201 MOZ_ASSERT(buf
.len
> 0);
202 nsresult rv
= digestFromManifest
.ValidateLength();
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
;
216 rv
= digest
.Begin(digestFromManifest
.mAlgorithm
);
217 NS_ENSURE_SUCCESS(rv
, rv
);
219 uint64_t totalBytesRead
= 0;
222 rv
= stream
->Read(BitwiseCast
<char*, unsigned char*>(buf
.data
), buf
.len
,
224 NS_ENSURE_SUCCESS(rv
, rv
);
226 if (bytesRead
== 0) {
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
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
;
258 nsresult
VerifyEntryContentDigest(nsIZipReader
* zip
,
259 const nsACString
& aFilename
,
260 const DigestWithAlgorithm
& digestFromManifest
,
262 nsCOMPtr
<nsIInputStream
> stream
;
263 nsresult rv
= zip
->GetInputStream(aFilename
, getter_AddRefs(stream
));
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) {
276 size_t previousLength
= 0;
277 size_t currentLength
= 0;
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
;
312 // not a 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
;
348 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID
; // colon with no name
350 if (curLine
[nameEnd
- 1] != ' ') break;
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
] == ' ') {
362 curLine
.Right(attrValue
, curLineLength
- valueStart
);
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
373 nsAutoCString curLine
;
374 nsresult rv
= ReadLine(nextLineStart
, curLine
, false);
378 if (!curLine
.Equals(expectedHeader
)) {
379 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID
;
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
) {
407 digestNameToFind
= "sha256-digest-manifest";
410 digestNameToFind
= "sha1-digest-manifest";
413 MOZ_ASSERT_UNREACHABLE("bad argument to ParseSF");
414 return NS_ERROR_FAILURE
;
417 const char* nextLineStart
= filebuf
;
419 CheckManifestVersion(nextLineStart
, nsLiteralCString(JAR_SF_HEADER
));
425 nsAutoCString curLine
;
426 rv
= ReadLine(nextLineStart
, curLine
);
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
);
444 if (attrName
.EqualsIgnoreCase(digestNameToFind
)) {
445 rv
= Base64Decode(attrValue
, mfDigest
);
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
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
) {
482 digestNameToFind
= "sha256-digest";
485 digestNameToFind
= "sha1-digest";
488 MOZ_ASSERT_UNREACHABLE("bad argument to ParseMF");
489 return NS_ERROR_FAILURE
;
492 const char* nextLineStart
= filebuf
;
494 CheckManifestVersion(nextLineStart
, nsLiteralCString(JAR_MF_HEADER
));
499 // Skip the rest of the header section, which ends with a blank line.
503 rv
= ReadLine(nextLineStart
, line
);
507 } while (line
.Length() > 0);
509 // Manifest containing no file entries is OK, though useless.
510 if (*nextLineStart
== '\0') {
515 nsAutoCString curItemName
;
516 nsAutoCString digest
;
519 nsAutoCString curLine
;
520 rv
= ReadLine(nextLineStart
, curLine
);
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
)) {
542 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID
;
545 // Verify that the entry's content digest matches the digest from this
547 DigestWithAlgorithm digestWithAlgorithm
= {digest
, digestAlgorithm
};
548 rv
= VerifyEntryContentDigest(zip
, curItemName
, digestWithAlgorithm
, buf
);
553 mfItems
.PutEntry(curItemName
);
555 if (*nextLineStart
== '\0') {
560 // reset so we know we haven't encountered either of these for the next
562 curItemName
.Truncate();
565 continue; // skip the rest of the loop below
568 nsAutoCString attrName
;
569 nsAutoCString attrValue
;
570 rv
= ParseAttribute(curLine
, attrName
, attrValue
);
575 // Lines to look for:
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
);
585 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID
;
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
;
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
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
);
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.
660 if (result
!= Success
) {
661 return mozilla::psm::GetXPCOMFromNSSError(MapResultToPRErrorCode(result
));
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
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
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
) {
682 int numSigners
= NSS_CMSSignedData_SignerInfoCount(signedData
);
683 if (numSigners
< 1) {
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
);
694 if (digestAlgorithm
== digestAlgOID
->offset
) {
701 Span
<const uint8_t> GetPKCS7SignerCert(
702 NSSCMSSignerInfo
* signerInfo
,
703 nsTArray
<Span
<const uint8_t>>& collectedCerts
) {
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
) {
713 CERTIssuerAndSN
* issuerAndSN
= signerInfo
->signerIdentifier
.id
.issuerAndSN
;
718 mozilla::pkix::Result result
=
719 issuer
.Init(issuerAndSN
->derIssuer
.data
, issuerAndSN
->derIssuer
.len
);
720 if (result
!= Success
) {
724 result
= serialNumber
.Init(issuerAndSN
->serialNumber
.data
,
725 issuerAndSN
->serialNumber
.len
);
726 if (result
!= Success
) {
729 for (const auto& certDER
: collectedCerts
) {
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
) {
742 if (InputsAreEqual(issuer
, cert
.GetIssuer()) &&
743 InputsAreEqual(serialNumber
, cert
.GetSerialNumber())) {
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,
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);
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
));
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
;
804 signerInfo
= GetSignerInfoForDigestAlgorithm(signedData
, SEC_OID_SHA1
);
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
;
824 VerifyCertificate(signerCertSpan
, trustedRoot
, std::move(collectedCerts
));
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) !=
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
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
{
864 explicit CoseVerificationContext(AppTrustedRoot aTrustedRoot
)
865 : mTrustedRoot(aTrustedRoot
) {}
866 ~CoseVerificationContext() = default;
868 AppTrustedRoot
GetTrustedRoot() { return mTrustedRoot
; }
869 void SetCert(Span
<const uint8_t> certDER
) {
871 mCertDER
.AppendElements(certDER
);
874 nsTArray
<uint8_t> TakeCert() { return std::move(mCertDER
); }
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
,
890 if (!ctx
|| !aPayload
|| !aEECert
|| !aSignature
) {
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
;
899 uint32_t hash_length
;
900 SECItem param
= {siBuffer
, nullptr, 0};
901 switch (aSignatureAlgorithm
) {
903 mechanism
= CKM_ECDSA
;
904 oid
= SEC_OID_SHA256
;
905 hash_length
= SHA256_LENGTH
;
908 mechanism
= CKM_ECDSA
;
909 oid
= SEC_OID_SHA384
;
910 hash_length
= SHA384_LENGTH
;
913 mechanism
= CKM_ECDSA
;
914 oid
= SEC_OID_SHA512
;
915 hash_length
= SHA512_LENGTH
;
921 uint8_t hashBuf
[HASH_LENGTH_MAX
];
922 SECStatus rv
= PK11_HashBuf(oid
, hashBuf
, aPayload
, aPayloadLen
);
923 if (rv
!= SECSuccess
) {
926 SECItem hashItem
= {siBuffer
, hashBuf
, hash_length
};
928 if (certInput
.Init(aEECert
, aEECertLen
) != Success
) {
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
) {
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
));
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
,
950 if (rv
!= SECSuccess
) {
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
};
962 VerifyCertificate(certSpan
, aTrustedRoot
, std::move(collectedCerts
));
964 if (NS_FAILED(nrv
)) {
968 // Passing back the signing certificate in form of the DER cert.
969 context
->SetCert(certSpan
);
970 if (NS_FAILED(nrv
)) {
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
983 ScopedAutoSECItem
buf(128 * 1024);
985 nsTHashtable
<nsCStringHashKey
> items
;
988 ParseMF(BitwiseCast
<char*, unsigned char*>(aManifestBuffer
.data
), aZip
,
989 aDigestToUse
, items
, buf
);
994 // Verify every entry in the file.
995 nsCOMPtr
<nsIUTF8StringEnumerator
> entries
;
996 rv
= aZip
->FindEntries(""_ns
, getter_AddRefs(entries
));
1001 return NS_ERROR_UNEXPECTED
;
1006 rv
= entries
->HasMore(&hasMore
);
1007 NS_ENSURE_SUCCESS(rv
, rv
);
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
)) {
1030 // Entries with names that end in "/" are directory entries, which are not
1033 // Since bug 1415991 we don't support unpacked JARs. The "/" entries are
1034 // therefore harmless.
1035 if (entryFilename
.Last() == '/') {
1039 nsCStringHashKey
* item
= items
.GetEntry(entryFilename
);
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
;
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
{
1069 explicit SignaturePolicy(int32_t preference
)
1070 : mProcessCose(true),
1071 mCoseRequired(false),
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;
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
) {
1097 if (aHashAlg
== SEC_OID_SHA1
&& 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();
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
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
)) {
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();
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
)) {
1201 nsTArray
<uint8_t> sfCalculatedSHA256Digest
;
1202 rv
= Digest::DigestBuf(SEC_OID_SHA256
, sfBuffer
.data
, sfBuffer
.len
- 1,
1203 sfCalculatedSHA256Digest
);
1204 if (NS_FAILED(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
)) {
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
,
1226 if (NS_FAILED(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
,
1237 if (NS_FAILED(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();
1259 aHashAlgorithm
= digestToUse
;
1263 class AppSignatureInfo final
: public nsIAppSignatureInfo
{
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();
1277 NS_IMETHODIMP
GetSignatureAlgorithm(
1278 nsIAppSignatureInfo::SignatureAlgorithm
* signatureAlgorithm
) override
{
1279 *signatureAlgorithm
= mSignatureAlgorithm
;
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
);
1299 *aZipReader
= nullptr;
1302 aSignatureInfos
.Clear();
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
)) {
1333 if (aPolicy
.ProcessCOSE()) {
1334 rv
= VerifyCOSESignature(aTrustedRoot
, zip
, aPolicy
, ignoredFiles
,
1335 coseVerified
, coseCertDER
);
1336 if (NS_FAILED(rv
)) {
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
;
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
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
) {
1383 signatureAlgorithm
=
1384 nsIAppSignatureInfo::SignatureAlgorithm::PKCS7_WITH_SHA1
;
1386 case SEC_OID_SHA256
:
1387 signatureAlgorithm
=
1388 nsIAppSignatureInfo::SignatureAlgorithm::PKCS7_WITH_SHA256
;
1391 return NS_ERROR_FAILURE
;
1393 aSignatureInfos
.AppendElement(
1394 new AppSignatureInfo(std::move(signerCert
), signatureAlgorithm
));
1400 class OpenSignedAppFileTask final
: public CryptoTask
{
1402 OpenSignedAppFileTask(AppTrustedRoot aTrustedRoot
, nsIFile
* aJarFile
,
1403 SignaturePolicy aPolicy
,
1404 nsIOpenSignedAppFileCallback
* aCallback
)
1405 : mTrustedRoot(aTrustedRoot
),
1408 mCallback(new nsMainThreadPtrHolder
<nsIOpenSignedAppFileCallback
>(
1409 "OpenSignedAppFileTask::mCallback", aCallback
)) {}
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
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
;
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();