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"
10 #include "pkix/pkixnss.h"
11 #include "pkix/ScopedPtr.h"
12 #include "mozilla/RefPtr.h"
13 #include "CryptoTask.h"
14 #include "AppTrustDomain.h"
15 #include "nsComponentManagerUtils.h"
17 #include "nsDataSignatureVerifier.h"
18 #include "nsHashKeys.h"
20 #include "nsIFileStreams.h"
21 #include "nsIInputStream.h"
22 #include "nsIStringEnumerator.h"
23 #include "nsIZipReader.h"
24 #include "nsNetUtil.h"
25 #include "nsNSSCertificate.h"
26 #include "nsProxyRelease.h"
27 #include "NSSCertDBTrustDomain.h"
29 #include "nsTHashtable.h"
38 using namespace mozilla::pkix
;
39 using namespace mozilla
;
40 using namespace mozilla::psm
;
43 extern PRLogModuleInfo
* gPIPNSSLog
;
48 // Reads a maximum of 1MB from a stream into the supplied buffer.
49 // The reason for the 1MB limit is because this function is used to read
50 // signature-related files and we want to avoid OOM. The uncompressed length of
51 // an entry can be hundreds of times larger than the compressed version,
52 // especially if someone has specifically crafted the entry to cause OOM or to
53 // consume massive amounts of disk space.
55 // @param stream The input stream to read from.
56 // @param buf The buffer that we read the stream into, which must have
57 // already been allocated.
59 ReadStream(const nsCOMPtr
<nsIInputStream
>& stream
, /*out*/ SECItem
& buf
)
61 // The size returned by Available() might be inaccurate so we need
62 // to check that Available() matches up with the actual length of
65 nsresult rv
= stream
->Available(&length
);
66 if (NS_WARN_IF(NS_FAILED(rv
))) {
70 // Cap the maximum accepted size of signature-related files at 1MB (which is
71 // still crazily huge) to avoid OOM. The uncompressed length of an entry can be
72 // hundreds of times larger than the compressed version, especially if
73 // someone has speifically crafted the entry to cause OOM or to consume
74 // massive amounts of disk space.
75 static const uint32_t MAX_LENGTH
= 1024 * 1024;
76 if (length
> MAX_LENGTH
) {
77 return NS_ERROR_FILE_TOO_BIG
;
80 // With bug 164695 in mind we +1 to leave room for null-terminating
82 SECITEM_AllocItem(buf
, static_cast<uint32_t>(length
+ 1));
84 // buf.len == length + 1. We attempt to read length + 1 bytes
85 // instead of length, so that we can check whether the metadata for
86 // the entry is incorrect.
88 rv
= stream
->Read(char_ptr_cast(buf
.data
), buf
.len
, &bytesRead
);
89 if (NS_WARN_IF(NS_FAILED(rv
))) {
92 if (bytesRead
!= length
) {
93 return NS_ERROR_FILE_CORRUPTED
;
96 buf
.data
[buf
.len
- 1] = 0; // null-terminate
101 // Finds exactly one (signature metadata) entry that matches the given
102 // search pattern, and then load it. Fails if there are no matches or if
103 // there is more than one match. If bugDigest is not null then on success
104 // bufDigest will contain the SHA-1 digeset of the entry.
106 FindAndLoadOneEntry(nsIZipReader
* zip
,
107 const nsACString
& searchPattern
,
108 /*out*/ nsACString
& filename
,
109 /*out*/ SECItem
& buf
,
110 /*optional, out*/ Digest
* bufDigest
)
112 nsCOMPtr
<nsIUTF8StringEnumerator
> files
;
113 nsresult rv
= zip
->FindEntries(searchPattern
, getter_AddRefs(files
));
114 if (NS_FAILED(rv
) || !files
) {
115 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID
;
119 rv
= files
->HasMore(&more
);
120 NS_ENSURE_SUCCESS(rv
, rv
);
122 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID
;
125 rv
= files
->GetNext(filename
);
126 NS_ENSURE_SUCCESS(rv
, rv
);
128 // Check if there is more than one match, if so then error!
129 rv
= files
->HasMore(&more
);
130 NS_ENSURE_SUCCESS(rv
, rv
);
132 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID
;
135 nsCOMPtr
<nsIInputStream
> stream
;
136 rv
= zip
->GetInputStream(filename
, getter_AddRefs(stream
));
137 NS_ENSURE_SUCCESS(rv
, rv
);
139 rv
= ReadStream(stream
, buf
);
140 if (NS_WARN_IF(NS_FAILED(rv
))) {
141 return NS_ERROR_SIGNED_JAR_ENTRY_INVALID
;
145 rv
= bufDigest
->DigestBuf(SEC_OID_SHA1
, buf
.data
, buf
.len
- 1);
146 NS_ENSURE_SUCCESS(rv
, rv
);
152 // Verify the digest of an entry. We avoid loading the entire entry into memory
153 // at once, which would require memory in proportion to the size of the largest
154 // entry. Instead, we require only a small, fixed amount of memory.
156 // @param digestFromManifest The digest that we're supposed to check the file's
157 // contents against, from the manifest
158 // @param buf A scratch buffer that we use for doing the I/O, which must have
159 // already been allocated. The size of this buffer is the unit
162 VerifyEntryContentDigest(nsIZipReader
* zip
, const nsACString
& aFilename
,
163 const SECItem
& digestFromManifest
, SECItem
& buf
)
165 MOZ_ASSERT(buf
.len
> 0);
166 if (digestFromManifest
.len
!= SHA1_LENGTH
)
167 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID
;
171 nsCOMPtr
<nsIInputStream
> stream
;
172 rv
= zip
->GetInputStream(aFilename
, getter_AddRefs(stream
));
174 return NS_ERROR_SIGNED_JAR_ENTRY_MISSING
;
178 rv
= stream
->Available(&len64
);
179 NS_ENSURE_SUCCESS(rv
, rv
);
180 if (len64
> UINT32_MAX
) {
181 return NS_ERROR_SIGNED_JAR_ENTRY_TOO_LARGE
;
184 ScopedPK11Context
digestContext(PK11_CreateDigestContext(SEC_OID_SHA1
));
185 if (!digestContext
) {
186 return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
189 rv
= MapSECStatus(PK11_DigestBegin(digestContext
));
190 NS_ENSURE_SUCCESS(rv
, rv
);
192 uint64_t totalBytesRead
= 0;
195 rv
= stream
->Read(char_ptr_cast(buf
.data
), buf
.len
, &bytesRead
);
196 NS_ENSURE_SUCCESS(rv
, rv
);
198 if (bytesRead
== 0) {
202 totalBytesRead
+= bytesRead
;
203 if (totalBytesRead
>= UINT32_MAX
) {
204 return NS_ERROR_SIGNED_JAR_ENTRY_TOO_LARGE
;
207 rv
= MapSECStatus(PK11_DigestOp(digestContext
, buf
.data
, bytesRead
));
208 NS_ENSURE_SUCCESS(rv
, rv
);
211 if (totalBytesRead
!= len64
) {
212 // The metadata we used for Available() doesn't match the actual size of
214 return NS_ERROR_SIGNED_JAR_ENTRY_INVALID
;
217 // Verify that the digests match.
219 rv
= digest
.End(SEC_OID_SHA1
, digestContext
);
220 NS_ENSURE_SUCCESS(rv
, rv
);
222 if (SECITEM_CompareItem(&digestFromManifest
, &digest
.get()) != SECEqual
) {
223 return NS_ERROR_SIGNED_JAR_MODIFIED_ENTRY
;
229 // On input, nextLineStart is the start of the current line. On output,
230 // nextLineStart is the start of the next line.
232 ReadLine(/*in/out*/ const char* & nextLineStart
, /*out*/ nsCString
& line
,
233 bool allowContinuations
= true)
236 size_t previousLength
= 0;
237 size_t currentLength
= 0;
239 const char* eol
= PL_strpbrk(nextLineStart
, "\r\n");
241 if (!eol
) { // Reached end of file before newline
242 eol
= nextLineStart
+ strlen(nextLineStart
);
245 previousLength
= currentLength
;
246 line
.Append(nextLineStart
, eol
- nextLineStart
);
247 currentLength
= line
.Length();
249 // The spec says "No line may be longer than 72 bytes (not characters)"
250 // in its UTF8-encoded form.
251 static const size_t lineLimit
= 72;
252 if (currentLength
- previousLength
> lineLimit
) {
253 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID
;
256 // The spec says: "Implementations should support 65535-byte
257 // (not character) header values..."
258 if (currentLength
> 65535) {
259 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID
;
272 // not a continuation
277 if (!allowContinuations
) {
278 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID
;
281 ++nextLineStart
; // skip space and keep appending
285 // The header strings are defined in the JAR specification.
286 #define JAR_MF_SEARCH_STRING "(M|/M)ETA-INF/(M|m)(ANIFEST|anifest).(MF|mf)$"
287 #define JAR_SF_SEARCH_STRING "(M|/M)ETA-INF/*.(SF|sf)$"
288 #define JAR_RSA_SEARCH_STRING "(M|/M)ETA-INF/*.(RSA|rsa)$"
289 #define JAR_MF_HEADER "Manifest-Version: 1.0"
290 #define JAR_SF_HEADER "Signature-Version: 1.0"
293 ParseAttribute(const nsAutoCString
& curLine
,
294 /*out*/ nsAutoCString
& attrName
,
295 /*out*/ nsAutoCString
& attrValue
)
297 // Find the colon that separates the name from the value.
298 int32_t colonPos
= curLine
.FindChar(':');
299 if (colonPos
== kNotFound
) {
300 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID
;
303 // set attrName to the name, skipping spaces between the name and colon
304 int32_t nameEnd
= colonPos
;
307 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID
; // colon with no name
309 if (curLine
[nameEnd
- 1] != ' ')
313 curLine
.Left(attrName
, nameEnd
);
315 // Set attrValue to the value, skipping spaces between the colon and the
316 // value. The value may be empty.
317 int32_t valueStart
= colonPos
+ 1;
318 int32_t curLineLength
= curLine
.Length();
319 while (valueStart
!= curLineLength
&& curLine
[valueStart
] == ' ') {
322 curLine
.Right(attrValue
, curLineLength
- valueStart
);
327 // Parses the version line of the MF or SF header.
329 CheckManifestVersion(const char* & nextLineStart
,
330 const nsACString
& expectedHeader
)
332 // The JAR spec says: "Manifest-Version and Signature-Version must be first,
333 // and in exactly that case (so that they can be recognized easily as magic
335 nsAutoCString curLine
;
336 nsresult rv
= ReadLine(nextLineStart
, curLine
, false);
340 if (!curLine
.Equals(expectedHeader
)) {
341 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID
;
346 // Parses a signature file (SF) as defined in the JDK 8 JAR Specification.
348 // The SF file *must* contain exactly one SHA1-Digest-Manifest attribute in
349 // the main section. All other sections are ignored. This means that this will
350 // NOT parse old-style signature files that have separate digests per entry.
351 // The JDK8 x-Digest-Manifest variant is better because:
353 // (1) It allows us to follow the principle that we should minimize the
354 // processing of data that we do before we verify its signature. In
355 // particular, with the x-Digest-Manifest style, we can verify the digest
356 // of MANIFEST.MF before we parse it, which prevents malicious JARs
357 // exploiting our MANIFEST.MF parser.
358 // (2) It is more time-efficient and space-efficient to have one
359 // x-Digest-Manifest instead of multiple x-Digest values.
361 // In order to get benefit (1), we do NOT implement the fallback to the older
362 // mechanism as the spec requires/suggests. Also, for simplity's sake, we only
363 // support exactly one SHA1-Digest-Manifest attribute, and no other
366 // filebuf must be null-terminated. On output, mfDigest will contain the
367 // decoded value of SHA1-Digest-Manifest.
369 ParseSF(const char* filebuf
, /*out*/ SECItem
& mfDigest
)
373 const char* nextLineStart
= filebuf
;
374 rv
= CheckManifestVersion(nextLineStart
, NS_LITERAL_CSTRING(JAR_SF_HEADER
));
378 // Find SHA1-Digest-Manifest
380 nsAutoCString curLine
;
381 rv
= ReadLine(nextLineStart
, curLine
);
386 if (curLine
.Length() == 0) {
387 // End of main section (blank line or end-of-file), and no
388 // SHA1-Digest-Manifest found.
389 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID
;
392 nsAutoCString attrName
;
393 nsAutoCString attrValue
;
394 rv
= ParseAttribute(curLine
, attrName
, attrValue
);
399 if (attrName
.LowerCaseEqualsLiteral("sha1-digest-manifest")) {
400 rv
= MapSECStatus(ATOB_ConvertAsciiToItem(&mfDigest
, attrValue
.get()));
405 // There could be multiple SHA1-Digest-Manifest attributes, which
406 // would be an error, but it's better to just skip any erroneous
407 // duplicate entries rather than trying to detect them, because:
409 // (1) It's simpler, and simpler generally means more secure
410 // (2) An attacker can't make us accept a JAR we would otherwise
411 // reject just by adding additional SHA1-Digest-Manifest
416 // ignore unrecognized attributes
422 // Parses MANIFEST.MF. The filenames of all entries will be returned in
423 // mfItems. buf must be a pre-allocated scratch buffer that is used for doing
426 ParseMF(const char* filebuf
, nsIZipReader
* zip
,
427 /*out*/ nsTHashtable
<nsCStringHashKey
> & mfItems
,
428 ScopedAutoSECItem
& buf
)
432 const char* nextLineStart
= filebuf
;
434 rv
= CheckManifestVersion(nextLineStart
, NS_LITERAL_CSTRING(JAR_MF_HEADER
));
439 // Skip the rest of the header section, which ends with a blank line.
443 rv
= ReadLine(nextLineStart
, line
);
447 } while (line
.Length() > 0);
449 // Manifest containing no file entries is OK, though useless.
450 if (*nextLineStart
== '\0') {
455 nsAutoCString curItemName
;
456 ScopedAutoSECItem digest
;
459 nsAutoCString curLine
;
460 rv
= ReadLine(nextLineStart
, curLine
);
461 NS_ENSURE_SUCCESS(rv
, rv
);
463 if (curLine
.Length() == 0) {
464 // end of section (blank line or end-of-file)
466 if (curItemName
.Length() == 0) {
467 // '...Each section must start with an attribute with the name as
468 // "Name",...', so every section must have a Name attribute.
469 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID
;
472 if (digest
.len
== 0) {
473 // We require every entry to have a digest, since we require every
474 // entry to be signed and we don't allow duplicate entries.
475 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID
;
478 if (mfItems
.Contains(curItemName
)) {
480 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID
;
483 // Verify that the entry's content digest matches the digest from this
485 rv
= VerifyEntryContentDigest(zip
, curItemName
, digest
, buf
);
489 mfItems
.PutEntry(curItemName
);
491 if (*nextLineStart
== '\0') // end-of-file
494 // reset so we know we haven't encountered either of these for the next
496 curItemName
.Truncate();
499 continue; // skip the rest of the loop below
502 nsAutoCString attrName
;
503 nsAutoCString attrValue
;
504 rv
= ParseAttribute(curLine
, attrName
, attrValue
);
509 // Lines to look for:
512 if (attrName
.LowerCaseEqualsLiteral("sha1-digest"))
514 if (digest
.len
> 0) // multiple SHA1 digests in section
515 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID
;
517 rv
= MapSECStatus(ATOB_ConvertAsciiToItem(&digest
, attrValue
.get()));
519 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID
;
524 // (2) Name: associates this manifest section with a file in the jar.
525 if (attrName
.LowerCaseEqualsLiteral("name"))
527 if (MOZ_UNLIKELY(curItemName
.Length() > 0)) // multiple names in section
528 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID
;
530 if (MOZ_UNLIKELY(attrValue
.Length() == 0))
531 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID
;
533 curItemName
= attrValue
;
538 // (3) Magic: the only other must-understand attribute
539 if (attrName
.LowerCaseEqualsLiteral("magic")) {
540 // We don't understand any magic, so we can't verify an entry that
541 // requires magic. Since we require every entry to have a valid
542 // signature, we have no choice but to reject the entry.
543 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID
;
546 // unrecognized attributes must be ignored
552 struct VerifyCertificateContext
{
553 AppTrustedRoot trustedRoot
;
554 ScopedCERTCertList
& builtChain
;
558 VerifyCertificate(CERTCertificate
* signerCert
, void* voidContext
, void* pinArg
)
560 // TODO: null pinArg is tolerated.
561 if (NS_WARN_IF(!signerCert
) || NS_WARN_IF(!voidContext
)) {
562 return NS_ERROR_INVALID_ARG
;
564 const VerifyCertificateContext
& context
=
565 *reinterpret_cast<const VerifyCertificateContext
*>(voidContext
);
567 AppTrustDomain
trustDomain(context
.builtChain
, pinArg
);
568 if (trustDomain
.SetTrustedRoot(context
.trustedRoot
) != SECSuccess
) {
569 return MapSECStatus(SECFailure
);
572 Result rv
= certDER
.Init(signerCert
->derCert
.data
, signerCert
->derCert
.len
);
574 return mozilla::psm::GetXPCOMFromNSSError(MapResultToPRErrorCode(rv
));
577 rv
= BuildCertChain(trustDomain
, certDER
, Now(),
578 EndEntityOrCA::MustBeEndEntity
,
579 KeyUsage::digitalSignature
,
580 KeyPurposeId::id_kp_codeSigning
,
581 CertPolicyId::anyPolicy
,
582 nullptr/*stapledOCSPResponse*/);
584 return mozilla::psm::GetXPCOMFromNSSError(MapResultToPRErrorCode(rv
));
591 VerifySignature(AppTrustedRoot trustedRoot
, const SECItem
& buffer
,
592 const SECItem
& detachedDigest
,
593 /*out*/ ScopedCERTCertList
& builtChain
)
595 VerifyCertificateContext context
= { trustedRoot
, builtChain
};
596 // XXX: missing pinArg
597 return VerifyCMSDetachedSignatureIncludingCertificate(buffer
, detachedDigest
,
603 OpenSignedAppFile(AppTrustedRoot aTrustedRoot
, nsIFile
* aJarFile
,
604 /*out, optional */ nsIZipReader
** aZipReader
,
605 /*out, optional */ nsIX509Cert
** aSignerCert
)
607 NS_ENSURE_ARG_POINTER(aJarFile
);
610 *aZipReader
= nullptr;
614 *aSignerCert
= nullptr;
619 static NS_DEFINE_CID(kZipReaderCID
, NS_ZIPREADER_CID
);
620 nsCOMPtr
<nsIZipReader
> zip
= do_CreateInstance(kZipReaderCID
, &rv
);
621 NS_ENSURE_SUCCESS(rv
, rv
);
623 rv
= zip
->Open(aJarFile
);
624 NS_ENSURE_SUCCESS(rv
, rv
);
626 // Signature (RSA) file
627 nsAutoCString sigFilename
;
628 ScopedAutoSECItem sigBuffer
;
629 rv
= FindAndLoadOneEntry(zip
, nsLiteralCString(JAR_RSA_SEARCH_STRING
),
630 sigFilename
, sigBuffer
, nullptr);
632 return NS_ERROR_SIGNED_JAR_NOT_SIGNED
;
635 // Signature (SF) file
636 nsAutoCString sfFilename
;
637 ScopedAutoSECItem sfBuffer
;
638 Digest sfCalculatedDigest
;
639 rv
= FindAndLoadOneEntry(zip
, NS_LITERAL_CSTRING(JAR_SF_SEARCH_STRING
),
640 sfFilename
, sfBuffer
, &sfCalculatedDigest
);
642 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID
;
645 sigBuffer
.type
= siBuffer
;
646 ScopedCERTCertList builtChain
;
647 rv
= VerifySignature(aTrustedRoot
, sigBuffer
, sfCalculatedDigest
.get(),
653 ScopedAutoSECItem mfDigest
;
654 rv
= ParseSF(char_ptr_cast(sfBuffer
.data
), mfDigest
);
659 // Manifest (MF) file
660 nsAutoCString mfFilename
;
661 ScopedAutoSECItem manifestBuffer
;
662 Digest mfCalculatedDigest
;
663 rv
= FindAndLoadOneEntry(zip
, NS_LITERAL_CSTRING(JAR_MF_SEARCH_STRING
),
664 mfFilename
, manifestBuffer
, &mfCalculatedDigest
);
669 if (SECITEM_CompareItem(&mfDigest
, &mfCalculatedDigest
.get()) != SECEqual
) {
670 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID
;
673 // Allocate the I/O buffer only once per JAR, instead of once per entry, in
674 // order to minimize malloc/free calls and in order to avoid fragmenting
676 ScopedAutoSECItem
buf(128 * 1024);
678 nsTHashtable
<nsCStringHashKey
> items
;
680 rv
= ParseMF(char_ptr_cast(manifestBuffer
.data
), zip
, items
, buf
);
685 // Verify every entry in the file.
686 nsCOMPtr
<nsIUTF8StringEnumerator
> entries
;
687 rv
= zip
->FindEntries(EmptyCString(), getter_AddRefs(entries
));
688 if (NS_SUCCEEDED(rv
) && !entries
) {
689 rv
= NS_ERROR_UNEXPECTED
;
697 rv
= entries
->HasMore(&hasMore
);
698 NS_ENSURE_SUCCESS(rv
, rv
);
704 nsAutoCString entryFilename
;
705 rv
= entries
->GetNext(entryFilename
);
706 NS_ENSURE_SUCCESS(rv
, rv
);
708 PR_LOG(gPIPNSSLog
, PR_LOG_DEBUG
, ("Verifying digests for %s",
709 entryFilename
.get()));
711 // The files that comprise the signature mechanism are not covered by the
714 // XXX: This is OK for a single signature, but doesn't work for
715 // multiple signatures, because the metadata for the other signatures
716 // is not signed either.
717 if (entryFilename
== mfFilename
||
718 entryFilename
== sfFilename
||
719 entryFilename
== sigFilename
) {
723 if (entryFilename
.Length() == 0) {
724 return NS_ERROR_SIGNED_JAR_ENTRY_INVALID
;
727 // Entries with names that end in "/" are directory entries, which are not
730 // XXX: As long as we don't unpack the JAR into the filesystem, the "/"
731 // entries are harmless. But, it is not clear what the security
732 // implications of directory entries are if/when we were to unpackage the
733 // JAR into the filesystem.
734 if (entryFilename
[entryFilename
.Length() - 1] == '/') {
738 nsCStringHashKey
* item
= items
.GetEntry(entryFilename
);
740 return NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY
;
743 // Remove the item so we can check for leftover items later
744 items
.RemoveEntry(entryFilename
);
747 // We verified that every entry that we require to be signed is signed. But,
748 // were there any missing entries--that is, entries that are mentioned in the
749 // manifest but missing from the archive?
750 if (items
.Count() != 0) {
751 return NS_ERROR_SIGNED_JAR_ENTRY_MISSING
;
754 // Return the reader to the caller if they want it
756 zip
.forget(aZipReader
);
759 // Return the signer's certificate to the reader if they want it.
760 // XXX: We should return an nsIX509CertList with the whole validated chain.
762 MOZ_ASSERT(CERT_LIST_HEAD(builtChain
));
763 nsCOMPtr
<nsIX509Cert
> signerCert
=
764 nsNSSCertificate::Create(CERT_LIST_HEAD(builtChain
)->cert
);
765 NS_ENSURE_TRUE(signerCert
, NS_ERROR_OUT_OF_MEMORY
);
766 signerCert
.forget(aSignerCert
);
773 VerifySignedManifest(AppTrustedRoot aTrustedRoot
,
774 nsIInputStream
* aManifestStream
,
775 nsIInputStream
* aSignatureStream
,
776 /*out, optional */ nsIX509Cert
** aSignerCert
)
778 NS_ENSURE_ARG(aManifestStream
);
779 NS_ENSURE_ARG(aSignatureStream
);
782 *aSignerCert
= nullptr;
785 // Load signature file in buffer
786 ScopedAutoSECItem signatureBuffer
;
787 nsresult rv
= ReadStream(aSignatureStream
, signatureBuffer
);
791 signatureBuffer
.type
= siBuffer
;
793 // Load manifest file in buffer
794 ScopedAutoSECItem manifestBuffer
;
795 rv
= ReadStream(aManifestStream
, manifestBuffer
);
800 // Calculate SHA1 digest of the manifest buffer
801 Digest manifestCalculatedDigest
;
802 rv
= manifestCalculatedDigest
.DigestBuf(SEC_OID_SHA1
,
804 manifestBuffer
.len
- 1); // buffer is null terminated
805 if (NS_WARN_IF(NS_FAILED(rv
))) {
809 // Get base64 encoded string from manifest buffer digest
810 ScopedPtr
<char, PORT_Free_string
> base64EncDigest(NSSBase64_EncodeItem(nullptr,
811 nullptr, 0, const_cast<SECItem
*>(&manifestCalculatedDigest
.get())));
812 if (NS_WARN_IF(!base64EncDigest
)) {
813 return NS_ERROR_OUT_OF_MEMORY
;
816 // Calculate SHA1 digest of the base64 encoded string
818 rv
= doubleDigest
.DigestBuf(SEC_OID_SHA1
,
819 reinterpret_cast<uint8_t*>(base64EncDigest
.get()),
820 strlen(base64EncDigest
.get()));
821 if (NS_WARN_IF(NS_FAILED(rv
))) {
825 // Verify the manifest signature (signed digest of the base64 encoded string)
826 ScopedCERTCertList builtChain
;
827 rv
= VerifySignature(aTrustedRoot
, signatureBuffer
,
828 doubleDigest
.get(), builtChain
);
833 // Return the signer's certificate to the reader if they want it.
835 MOZ_ASSERT(CERT_LIST_HEAD(builtChain
));
836 nsCOMPtr
<nsIX509Cert
> signerCert
=
837 nsNSSCertificate::Create(CERT_LIST_HEAD(builtChain
)->cert
);
838 if (NS_WARN_IF(!signerCert
)) {
839 return NS_ERROR_OUT_OF_MEMORY
;
842 signerCert
.forget(aSignerCert
);
848 class OpenSignedAppFileTask MOZ_FINAL
: public CryptoTask
851 OpenSignedAppFileTask(AppTrustedRoot aTrustedRoot
, nsIFile
* aJarFile
,
852 nsIOpenSignedAppFileCallback
* aCallback
)
853 : mTrustedRoot(aTrustedRoot
)
855 , mCallback(new nsMainThreadPtrHolder
<nsIOpenSignedAppFileCallback
>(aCallback
))
860 virtual nsresult
CalculateResult() MOZ_OVERRIDE
862 return OpenSignedAppFile(mTrustedRoot
, mJarFile
,
863 getter_AddRefs(mZipReader
),
864 getter_AddRefs(mSignerCert
));
867 // nsNSSCertificate implements nsNSSShutdownObject, so there's nothing that
868 // needs to be released
869 virtual void ReleaseNSSResources() MOZ_OVERRIDE
{ }
871 virtual void CallCallback(nsresult rv
) MOZ_OVERRIDE
873 (void) mCallback
->OpenSignedAppFileFinished(rv
, mZipReader
, mSignerCert
);
876 const AppTrustedRoot mTrustedRoot
;
877 const nsCOMPtr
<nsIFile
> mJarFile
;
878 nsMainThreadPtrHandle
<nsIOpenSignedAppFileCallback
> mCallback
;
879 nsCOMPtr
<nsIZipReader
> mZipReader
; // out
880 nsCOMPtr
<nsIX509Cert
> mSignerCert
; // out
883 class VerifySignedmanifestTask MOZ_FINAL
: public CryptoTask
886 VerifySignedmanifestTask(AppTrustedRoot aTrustedRoot
,
887 nsIInputStream
* aManifestStream
,
888 nsIInputStream
* aSignatureStream
,
889 nsIVerifySignedManifestCallback
* aCallback
)
890 : mTrustedRoot(aTrustedRoot
)
891 , mManifestStream(aManifestStream
)
892 , mSignatureStream(aSignatureStream
)
894 new nsMainThreadPtrHolder
<nsIVerifySignedManifestCallback
>(aCallback
))
899 virtual nsresult
CalculateResult() MOZ_OVERRIDE
901 return VerifySignedManifest(mTrustedRoot
, mManifestStream
,
902 mSignatureStream
, getter_AddRefs(mSignerCert
));
905 // nsNSSCertificate implements nsNSSShutdownObject, so there's nothing that
906 // needs to be released
907 virtual void ReleaseNSSResources() MOZ_OVERRIDE
{ }
909 virtual void CallCallback(nsresult rv
) MOZ_OVERRIDE
911 (void) mCallback
->VerifySignedManifestFinished(rv
, mSignerCert
);
914 const AppTrustedRoot mTrustedRoot
;
915 const nsCOMPtr
<nsIInputStream
> mManifestStream
;
916 const nsCOMPtr
<nsIInputStream
> mSignatureStream
;
917 nsMainThreadPtrHandle
<nsIVerifySignedManifestCallback
> mCallback
;
918 nsCOMPtr
<nsIX509Cert
> mSignerCert
; // out
921 } // unnamed namespace
924 nsNSSCertificateDB::OpenSignedAppFileAsync(
925 AppTrustedRoot aTrustedRoot
, nsIFile
* aJarFile
,
926 nsIOpenSignedAppFileCallback
* aCallback
)
928 NS_ENSURE_ARG_POINTER(aJarFile
);
929 NS_ENSURE_ARG_POINTER(aCallback
);
930 RefPtr
<OpenSignedAppFileTask
> task(new OpenSignedAppFileTask(aTrustedRoot
,
933 return task
->Dispatch("SignedJAR");
937 nsNSSCertificateDB::VerifySignedManifestAsync(
938 AppTrustedRoot aTrustedRoot
, nsIInputStream
* aManifestStream
,
939 nsIInputStream
* aSignatureStream
, nsIVerifySignedManifestCallback
* aCallback
)
941 NS_ENSURE_ARG_POINTER(aManifestStream
);
942 NS_ENSURE_ARG_POINTER(aSignatureStream
);
943 NS_ENSURE_ARG_POINTER(aCallback
);
945 RefPtr
<VerifySignedmanifestTask
> task(
946 new VerifySignedmanifestTask(aTrustedRoot
, aManifestStream
,
947 aSignatureStream
, aCallback
));
948 return task
->Dispatch("SignedManifest");