Tue, 06 Jan 2015 21:39:09 +0100
Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.
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 #ifdef MOZ_LOGGING
8 #define FORCE_PR_LOG 1
9 #endif
11 #include "nsNSSCertificateDB.h"
13 #include "pkix/pkix.h"
14 #include "mozilla/RefPtr.h"
15 #include "CryptoTask.h"
16 #include "AppTrustDomain.h"
17 #include "nsComponentManagerUtils.h"
18 #include "nsCOMPtr.h"
19 #include "nsHashKeys.h"
20 #include "nsIFile.h"
21 #include "nsIInputStream.h"
22 #include "nsIStringEnumerator.h"
23 #include "nsIZipReader.h"
24 #include "nsNSSCertificate.h"
25 #include "nsProxyRelease.h"
26 #include "nsString.h"
27 #include "nsTHashtable.h"
28 #include "ScopedNSSTypes.h"
30 #include "base64.h"
31 #include "certdb.h"
32 #include "secmime.h"
33 #include "plstr.h"
34 #include "prlog.h"
36 using namespace mozilla::pkix;
37 using namespace mozilla;
38 using namespace mozilla::psm;
40 #ifdef PR_LOGGING
41 extern PRLogModuleInfo* gPIPNSSLog;
42 #endif
44 namespace {
46 // Finds exactly one (signature metadata) entry that matches the given
47 // search pattern, and then load it. Fails if there are no matches or if
48 // there is more than one match. If bugDigest is not null then on success
49 // bufDigest will contain the SHA-1 digeset of the entry.
50 nsresult
51 FindAndLoadOneEntry(nsIZipReader * zip,
52 const nsACString & searchPattern,
53 /*out*/ nsACString & filename,
54 /*out*/ SECItem & buf,
55 /*optional, out*/ Digest * bufDigest)
56 {
57 nsCOMPtr<nsIUTF8StringEnumerator> files;
58 nsresult rv = zip->FindEntries(searchPattern, getter_AddRefs(files));
59 if (NS_FAILED(rv) || !files) {
60 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
61 }
63 bool more;
64 rv = files->HasMore(&more);
65 NS_ENSURE_SUCCESS(rv, rv);
66 if (!more) {
67 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
68 }
70 rv = files->GetNext(filename);
71 NS_ENSURE_SUCCESS(rv, rv);
73 // Check if there is more than one match, if so then error!
74 rv = files->HasMore(&more);
75 NS_ENSURE_SUCCESS(rv, rv);
76 if (more) {
77 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
78 }
80 nsCOMPtr<nsIInputStream> stream;
81 rv = zip->GetInputStream(filename, getter_AddRefs(stream));
82 NS_ENSURE_SUCCESS(rv, rv);
84 // The size returned by Available() might be inaccurate so we need to check
85 // that Available() matches up with the actual length of the file.
86 uint64_t len64;
87 rv = stream->Available(&len64);
88 NS_ENSURE_SUCCESS(rv, rv);
91 // Cap the maximum accepted size of signature-related files at 1MB (which is
92 // still crazily huge) to avoid OOM. The uncompressed length of an entry can be
93 // hundreds of times larger than the compressed version, especially if
94 // someone has speifically crafted the entry to cause OOM or to consume
95 // massive amounts of disk space.
96 //
97 // Also, keep in mind bug 164695 and that we must leave room for
98 // null-terminating the buffer.
99 static const uint32_t MAX_LENGTH = 1024 * 1024;
100 static_assert(MAX_LENGTH < UINT32_MAX, "MAX_LENGTH < UINT32_MAX");
101 NS_ENSURE_TRUE(len64 < MAX_LENGTH, NS_ERROR_FILE_CORRUPTED);
102 NS_ENSURE_TRUE(len64 < UINT32_MAX, NS_ERROR_FILE_CORRUPTED); // bug 164695
103 SECITEM_AllocItem(buf, static_cast<uint32_t>(len64 + 1));
105 // buf.len == len64 + 1. We attempt to read len64 + 1 bytes instead of len64,
106 // so that we can check whether the metadata in the ZIP for the entry is
107 // incorrect.
108 uint32_t bytesRead;
109 rv = stream->Read(char_ptr_cast(buf.data), buf.len, &bytesRead);
110 NS_ENSURE_SUCCESS(rv, rv);
111 if (bytesRead != len64) {
112 return NS_ERROR_SIGNED_JAR_ENTRY_INVALID;
113 }
115 buf.data[buf.len - 1] = 0; // null-terminate
117 if (bufDigest) {
118 rv = bufDigest->DigestBuf(SEC_OID_SHA1, buf.data, buf.len - 1);
119 NS_ENSURE_SUCCESS(rv, rv);
120 }
122 return NS_OK;
123 }
125 // Verify the digest of an entry. We avoid loading the entire entry into memory
126 // at once, which would require memory in proportion to the size of the largest
127 // entry. Instead, we require only a small, fixed amount of memory.
128 //
129 // @param digestFromManifest The digest that we're supposed to check the file's
130 // contents against, from the manifest
131 // @param buf A scratch buffer that we use for doing the I/O, which must have
132 // already been allocated. The size of this buffer is the unit
133 // size of our I/O.
134 nsresult
135 VerifyEntryContentDigest(nsIZipReader * zip, const nsACString & aFilename,
136 const SECItem & digestFromManifest, SECItem & buf)
137 {
138 MOZ_ASSERT(buf.len > 0);
139 if (digestFromManifest.len != SHA1_LENGTH)
140 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
142 nsresult rv;
144 nsCOMPtr<nsIInputStream> stream;
145 rv = zip->GetInputStream(aFilename, getter_AddRefs(stream));
146 if (NS_FAILED(rv)) {
147 return NS_ERROR_SIGNED_JAR_ENTRY_MISSING;
148 }
150 uint64_t len64;
151 rv = stream->Available(&len64);
152 NS_ENSURE_SUCCESS(rv, rv);
153 if (len64 > UINT32_MAX) {
154 return NS_ERROR_SIGNED_JAR_ENTRY_TOO_LARGE;
155 }
157 ScopedPK11Context digestContext(PK11_CreateDigestContext(SEC_OID_SHA1));
158 if (!digestContext) {
159 return PRErrorCode_to_nsresult(PR_GetError());
160 }
162 rv = MapSECStatus(PK11_DigestBegin(digestContext));
163 NS_ENSURE_SUCCESS(rv, rv);
165 uint64_t totalBytesRead = 0;
166 for (;;) {
167 uint32_t bytesRead;
168 rv = stream->Read(char_ptr_cast(buf.data), buf.len, &bytesRead);
169 NS_ENSURE_SUCCESS(rv, rv);
171 if (bytesRead == 0) {
172 break; // EOF
173 }
175 totalBytesRead += bytesRead;
176 if (totalBytesRead >= UINT32_MAX) {
177 return NS_ERROR_SIGNED_JAR_ENTRY_TOO_LARGE;
178 }
180 rv = MapSECStatus(PK11_DigestOp(digestContext, buf.data, bytesRead));
181 NS_ENSURE_SUCCESS(rv, rv);
182 }
184 if (totalBytesRead != len64) {
185 // The metadata we used for Available() doesn't match the actual size of
186 // the entry.
187 return NS_ERROR_SIGNED_JAR_ENTRY_INVALID;
188 }
190 // Verify that the digests match.
191 Digest digest;
192 rv = digest.End(SEC_OID_SHA1, digestContext);
193 NS_ENSURE_SUCCESS(rv, rv);
195 if (SECITEM_CompareItem(&digestFromManifest, &digest.get()) != SECEqual) {
196 return NS_ERROR_SIGNED_JAR_MODIFIED_ENTRY;
197 }
199 return NS_OK;
200 }
202 // On input, nextLineStart is the start of the current line. On output,
203 // nextLineStart is the start of the next line.
204 nsresult
205 ReadLine(/*in/out*/ const char* & nextLineStart, /*out*/ nsCString & line,
206 bool allowContinuations = true)
207 {
208 line.Truncate();
209 size_t previousLength = 0;
210 size_t currentLength = 0;
211 for (;;) {
212 const char* eol = PL_strpbrk(nextLineStart, "\r\n");
214 if (!eol) { // Reached end of file before newline
215 eol = nextLineStart + strlen(nextLineStart);
216 }
218 previousLength = currentLength;
219 line.Append(nextLineStart, eol - nextLineStart);
220 currentLength = line.Length();
222 // The spec says "No line may be longer than 72 bytes (not characters)"
223 // in its UTF8-encoded form.
224 static const size_t lineLimit = 72;
225 if (currentLength - previousLength > lineLimit) {
226 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
227 }
229 // The spec says: "Implementations should support 65535-byte
230 // (not character) header values..."
231 if (currentLength > 65535) {
232 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
233 }
235 if (*eol == '\r') {
236 ++eol;
237 }
238 if (*eol == '\n') {
239 ++eol;
240 }
242 nextLineStart = eol;
244 if (*eol != ' ') {
245 // not a continuation
246 return NS_OK;
247 }
249 // continuation
250 if (!allowContinuations) {
251 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
252 }
254 ++nextLineStart; // skip space and keep appending
255 }
256 }
258 // The header strings are defined in the JAR specification.
259 #define JAR_MF_SEARCH_STRING "(M|/M)ETA-INF/(M|m)(ANIFEST|anifest).(MF|mf)$"
260 #define JAR_SF_SEARCH_STRING "(M|/M)ETA-INF/*.(SF|sf)$"
261 #define JAR_RSA_SEARCH_STRING "(M|/M)ETA-INF/*.(RSA|rsa)$"
262 #define JAR_MF_HEADER "Manifest-Version: 1.0"
263 #define JAR_SF_HEADER "Signature-Version: 1.0"
265 nsresult
266 ParseAttribute(const nsAutoCString & curLine,
267 /*out*/ nsAutoCString & attrName,
268 /*out*/ nsAutoCString & attrValue)
269 {
270 // Find the colon that separates the name from the value.
271 int32_t colonPos = curLine.FindChar(':');
272 if (colonPos == kNotFound) {
273 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
274 }
276 // set attrName to the name, skipping spaces between the name and colon
277 int32_t nameEnd = colonPos;
278 for (;;) {
279 if (nameEnd == 0) {
280 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; // colon with no name
281 }
282 if (curLine[nameEnd - 1] != ' ')
283 break;
284 --nameEnd;
285 }
286 curLine.Left(attrName, nameEnd);
288 // Set attrValue to the value, skipping spaces between the colon and the
289 // value. The value may be empty.
290 int32_t valueStart = colonPos + 1;
291 int32_t curLineLength = curLine.Length();
292 while (valueStart != curLineLength && curLine[valueStart] == ' ') {
293 ++valueStart;
294 }
295 curLine.Right(attrValue, curLineLength - valueStart);
297 return NS_OK;
298 }
300 // Parses the version line of the MF or SF header.
301 nsresult
302 CheckManifestVersion(const char* & nextLineStart,
303 const nsACString & expectedHeader)
304 {
305 // The JAR spec says: "Manifest-Version and Signature-Version must be first,
306 // and in exactly that case (so that they can be recognized easily as magic
307 // strings)."
308 nsAutoCString curLine;
309 nsresult rv = ReadLine(nextLineStart, curLine, false);
310 if (NS_FAILED(rv)) {
311 return rv;
312 }
313 if (!curLine.Equals(expectedHeader)) {
314 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
315 }
316 return NS_OK;
317 }
319 // Parses a signature file (SF) as defined in the JDK 8 JAR Specification.
320 //
321 // The SF file *must* contain exactly one SHA1-Digest-Manifest attribute in
322 // the main section. All other sections are ignored. This means that this will
323 // NOT parse old-style signature files that have separate digests per entry.
324 // The JDK8 x-Digest-Manifest variant is better because:
325 //
326 // (1) It allows us to follow the principle that we should minimize the
327 // processing of data that we do before we verify its signature. In
328 // particular, with the x-Digest-Manifest style, we can verify the digest
329 // of MANIFEST.MF before we parse it, which prevents malicious JARs
330 // exploiting our MANIFEST.MF parser.
331 // (2) It is more time-efficient and space-efficient to have one
332 // x-Digest-Manifest instead of multiple x-Digest values.
333 //
334 // In order to get benefit (1), we do NOT implement the fallback to the older
335 // mechanism as the spec requires/suggests. Also, for simplity's sake, we only
336 // support exactly one SHA1-Digest-Manifest attribute, and no other
337 // algorithms.
338 //
339 // filebuf must be null-terminated. On output, mfDigest will contain the
340 // decoded value of SHA1-Digest-Manifest.
341 nsresult
342 ParseSF(const char* filebuf, /*out*/ SECItem & mfDigest)
343 {
344 nsresult rv;
346 const char* nextLineStart = filebuf;
347 rv = CheckManifestVersion(nextLineStart, NS_LITERAL_CSTRING(JAR_SF_HEADER));
348 if (NS_FAILED(rv))
349 return rv;
351 // Find SHA1-Digest-Manifest
352 for (;;) {
353 nsAutoCString curLine;
354 rv = ReadLine(nextLineStart, curLine);
355 if (NS_FAILED(rv)) {
356 return rv;
357 }
359 if (curLine.Length() == 0) {
360 // End of main section (blank line or end-of-file), and no
361 // SHA1-Digest-Manifest found.
362 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
363 }
365 nsAutoCString attrName;
366 nsAutoCString attrValue;
367 rv = ParseAttribute(curLine, attrName, attrValue);
368 if (NS_FAILED(rv)) {
369 return rv;
370 }
372 if (attrName.LowerCaseEqualsLiteral("sha1-digest-manifest")) {
373 rv = MapSECStatus(ATOB_ConvertAsciiToItem(&mfDigest, attrValue.get()));
374 if (NS_FAILED(rv)) {
375 return rv;
376 }
378 // There could be multiple SHA1-Digest-Manifest attributes, which
379 // would be an error, but it's better to just skip any erroneous
380 // duplicate entries rather than trying to detect them, because:
381 //
382 // (1) It's simpler, and simpler generally means more secure
383 // (2) An attacker can't make us accept a JAR we would otherwise
384 // reject just by adding additional SHA1-Digest-Manifest
385 // attributes.
386 break;
387 }
389 // ignore unrecognized attributes
390 }
392 return NS_OK;
393 }
395 // Parses MANIFEST.MF. The filenames of all entries will be returned in
396 // mfItems. buf must be a pre-allocated scratch buffer that is used for doing
397 // I/O.
398 nsresult
399 ParseMF(const char* filebuf, nsIZipReader * zip,
400 /*out*/ nsTHashtable<nsCStringHashKey> & mfItems,
401 ScopedAutoSECItem & buf)
402 {
403 nsresult rv;
405 const char* nextLineStart = filebuf;
407 rv = CheckManifestVersion(nextLineStart, NS_LITERAL_CSTRING(JAR_MF_HEADER));
408 if (NS_FAILED(rv)) {
409 return rv;
410 }
412 // Skip the rest of the header section, which ends with a blank line.
413 {
414 nsAutoCString line;
415 do {
416 rv = ReadLine(nextLineStart, line);
417 if (NS_FAILED(rv)) {
418 return rv;
419 }
420 } while (line.Length() > 0);
422 // Manifest containing no file entries is OK, though useless.
423 if (*nextLineStart == '\0') {
424 return NS_OK;
425 }
426 }
428 nsAutoCString curItemName;
429 ScopedAutoSECItem digest;
431 for (;;) {
432 nsAutoCString curLine;
433 rv = ReadLine(nextLineStart, curLine);
434 NS_ENSURE_SUCCESS(rv, rv);
436 if (curLine.Length() == 0) {
437 // end of section (blank line or end-of-file)
439 if (curItemName.Length() == 0) {
440 // '...Each section must start with an attribute with the name as
441 // "Name",...', so every section must have a Name attribute.
442 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
443 }
445 if (digest.len == 0) {
446 // We require every entry to have a digest, since we require every
447 // entry to be signed and we don't allow duplicate entries.
448 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
449 }
451 if (mfItems.Contains(curItemName)) {
452 // Duplicate entry
453 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
454 }
456 // Verify that the entry's content digest matches the digest from this
457 // MF section.
458 rv = VerifyEntryContentDigest(zip, curItemName, digest, buf);
459 if (NS_FAILED(rv))
460 return rv;
462 mfItems.PutEntry(curItemName);
464 if (*nextLineStart == '\0') // end-of-file
465 break;
467 // reset so we know we haven't encountered either of these for the next
468 // item yet.
469 curItemName.Truncate();
470 digest.reset();
472 continue; // skip the rest of the loop below
473 }
475 nsAutoCString attrName;
476 nsAutoCString attrValue;
477 rv = ParseAttribute(curLine, attrName, attrValue);
478 if (NS_FAILED(rv)) {
479 return rv;
480 }
482 // Lines to look for:
484 // (1) Digest:
485 if (attrName.LowerCaseEqualsLiteral("sha1-digest"))
486 {
487 if (digest.len > 0) // multiple SHA1 digests in section
488 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
490 rv = MapSECStatus(ATOB_ConvertAsciiToItem(&digest, attrValue.get()));
491 if (NS_FAILED(rv))
492 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
494 continue;
495 }
497 // (2) Name: associates this manifest section with a file in the jar.
498 if (attrName.LowerCaseEqualsLiteral("name"))
499 {
500 if (MOZ_UNLIKELY(curItemName.Length() > 0)) // multiple names in section
501 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
503 if (MOZ_UNLIKELY(attrValue.Length() == 0))
504 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
506 curItemName = attrValue;
508 continue;
509 }
511 // (3) Magic: the only other must-understand attribute
512 if (attrName.LowerCaseEqualsLiteral("magic")) {
513 // We don't understand any magic, so we can't verify an entry that
514 // requires magic. Since we require every entry to have a valid
515 // signature, we have no choice but to reject the entry.
516 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
517 }
519 // unrecognized attributes must be ignored
520 }
522 return NS_OK;
523 }
525 nsresult
526 VerifySignature(AppTrustedRoot trustedRoot,
527 const SECItem& buffer, const SECItem& detachedDigest,
528 /*out*/ mozilla::pkix::ScopedCERTCertList& builtChain)
529 {
530 mozilla::pkix::ScopedPtr<NSSCMSMessage, NSS_CMSMessage_Destroy>
531 cmsMsg(NSS_CMSMessage_CreateFromDER(const_cast<SECItem*>(&buffer), nullptr,
532 nullptr, nullptr, nullptr, nullptr,
533 nullptr));
534 if (!cmsMsg) {
535 return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING;
536 }
538 if (!NSS_CMSMessage_IsSigned(cmsMsg.get())) {
539 PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("CMS message isn't signed"));
540 return NS_ERROR_CMS_VERIFY_NOT_SIGNED;
541 }
543 NSSCMSContentInfo* cinfo = NSS_CMSMessage_ContentLevel(cmsMsg.get(), 0);
544 if (!cinfo) {
545 return NS_ERROR_CMS_VERIFY_NO_CONTENT_INFO;
546 }
548 // signedData is non-owning
549 NSSCMSSignedData* signedData =
550 reinterpret_cast<NSSCMSSignedData*>(NSS_CMSContentInfo_GetContent(cinfo));
551 if (!signedData) {
552 return NS_ERROR_CMS_VERIFY_NO_CONTENT_INFO;
553 }
555 // Set digest value.
556 if (NSS_CMSSignedData_SetDigestValue(signedData, SEC_OID_SHA1,
557 const_cast<SECItem*>(&detachedDigest))) {
558 return NS_ERROR_CMS_VERIFY_BAD_DIGEST;
559 }
561 // Parse the certificates into CERTCertificate objects held in memory, so that
562 // AppTrustDomain will be able to find them during path building.
563 mozilla::pkix::ScopedCERTCertList certs(CERT_NewCertList());
564 if (!certs) {
565 return NS_ERROR_OUT_OF_MEMORY;
566 }
567 if (signedData->rawCerts) {
568 for (size_t i = 0; signedData->rawCerts[i]; ++i) {
569 mozilla::pkix::ScopedCERTCertificate
570 cert(CERT_NewTempCertificate(CERT_GetDefaultCertDB(),
571 signedData->rawCerts[i], nullptr, false,
572 true));
573 // Skip certificates that fail to parse
574 if (cert) {
575 if (CERT_AddCertToListTail(certs.get(), cert.get()) == SECSuccess) {
576 cert.release(); // ownership transfered
577 } else {
578 return NS_ERROR_OUT_OF_MEMORY;
579 }
580 }
581 }
582 }
584 // Get the end-entity certificate.
585 int numSigners = NSS_CMSSignedData_SignerInfoCount(signedData);
586 if (NS_WARN_IF(numSigners != 1)) {
587 return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING;
588 }
589 // signer is non-owning.
590 NSSCMSSignerInfo* signer = NSS_CMSSignedData_GetSignerInfo(signedData, 0);
591 if (NS_WARN_IF(!signer)) {
592 return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING;
593 }
594 // cert is signerCert
595 CERTCertificate* signerCert =
596 NSS_CMSSignerInfo_GetSigningCertificate(signer, CERT_GetDefaultCertDB());
597 if (!signerCert) {
598 return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING;
599 }
601 // Verify certificate.
602 AppTrustDomain trustDomain(nullptr); // TODO: null pinArg
603 if (trustDomain.SetTrustedRoot(trustedRoot) != SECSuccess) {
604 return MapSECStatus(SECFailure);
605 }
606 if (BuildCertChain(trustDomain, signerCert, PR_Now(), MustBeEndEntity,
607 KeyUsage::digitalSignature,
608 SEC_OID_EXT_KEY_USAGE_CODE_SIGN,
609 SEC_OID_X509_ANY_POLICY, nullptr, builtChain)
610 != SECSuccess) {
611 return MapSECStatus(SECFailure);
612 }
614 // See NSS_CMSContentInfo_GetContentTypeOID, which isn't exported from NSS.
615 SECOidData* contentTypeOidData =
616 SECOID_FindOID(&signedData->contentInfo.contentType);
617 if (!contentTypeOidData) {
618 return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING;
619 }
621 return MapSECStatus(NSS_CMSSignerInfo_Verify(signer,
622 const_cast<SECItem*>(&detachedDigest),
623 &contentTypeOidData->oid));
624 }
626 NS_IMETHODIMP
627 OpenSignedAppFile(AppTrustedRoot aTrustedRoot, nsIFile* aJarFile,
628 /*out, optional */ nsIZipReader** aZipReader,
629 /*out, optional */ nsIX509Cert3** aSignerCert)
630 {
631 NS_ENSURE_ARG_POINTER(aJarFile);
633 if (aZipReader) {
634 *aZipReader = nullptr;
635 }
637 if (aSignerCert) {
638 *aSignerCert = nullptr;
639 }
641 nsresult rv;
643 static NS_DEFINE_CID(kZipReaderCID, NS_ZIPREADER_CID);
644 nsCOMPtr<nsIZipReader> zip = do_CreateInstance(kZipReaderCID, &rv);
645 NS_ENSURE_SUCCESS(rv, rv);
647 rv = zip->Open(aJarFile);
648 NS_ENSURE_SUCCESS(rv, rv);
650 // Signature (RSA) file
651 nsAutoCString sigFilename;
652 ScopedAutoSECItem sigBuffer;
653 rv = FindAndLoadOneEntry(zip, nsLiteralCString(JAR_RSA_SEARCH_STRING),
654 sigFilename, sigBuffer, nullptr);
655 if (NS_FAILED(rv)) {
656 return NS_ERROR_SIGNED_JAR_NOT_SIGNED;
657 }
659 // Signature (SF) file
660 nsAutoCString sfFilename;
661 ScopedAutoSECItem sfBuffer;
662 Digest sfCalculatedDigest;
663 rv = FindAndLoadOneEntry(zip, NS_LITERAL_CSTRING(JAR_SF_SEARCH_STRING),
664 sfFilename, sfBuffer, &sfCalculatedDigest);
665 if (NS_FAILED(rv)) {
666 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
667 }
669 sigBuffer.type = siBuffer;
670 mozilla::pkix::ScopedCERTCertList builtChain;
671 rv = VerifySignature(aTrustedRoot, sigBuffer, sfCalculatedDigest.get(),
672 builtChain);
673 if (NS_FAILED(rv)) {
674 return rv;
675 }
677 ScopedAutoSECItem mfDigest;
678 rv = ParseSF(char_ptr_cast(sfBuffer.data), mfDigest);
679 if (NS_FAILED(rv)) {
680 return rv;
681 }
683 // Manifest (MF) file
684 nsAutoCString mfFilename;
685 ScopedAutoSECItem manifestBuffer;
686 Digest mfCalculatedDigest;
687 rv = FindAndLoadOneEntry(zip, NS_LITERAL_CSTRING(JAR_MF_SEARCH_STRING),
688 mfFilename, manifestBuffer, &mfCalculatedDigest);
689 if (NS_FAILED(rv)) {
690 return rv;
691 }
693 if (SECITEM_CompareItem(&mfDigest, &mfCalculatedDigest.get()) != SECEqual) {
694 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
695 }
697 // Allocate the I/O buffer only once per JAR, instead of once per entry, in
698 // order to minimize malloc/free calls and in order to avoid fragmenting
699 // memory.
700 ScopedAutoSECItem buf(128 * 1024);
702 nsTHashtable<nsCStringHashKey> items;
704 rv = ParseMF(char_ptr_cast(manifestBuffer.data), zip, items, buf);
705 if (NS_FAILED(rv)) {
706 return rv;
707 }
709 // Verify every entry in the file.
710 nsCOMPtr<nsIUTF8StringEnumerator> entries;
711 rv = zip->FindEntries(EmptyCString(), getter_AddRefs(entries));
712 if (NS_SUCCEEDED(rv) && !entries) {
713 rv = NS_ERROR_UNEXPECTED;
714 }
715 if (NS_FAILED(rv)) {
716 return rv;
717 }
719 for (;;) {
720 bool hasMore;
721 rv = entries->HasMore(&hasMore);
722 NS_ENSURE_SUCCESS(rv, rv);
724 if (!hasMore) {
725 break;
726 }
728 nsAutoCString entryFilename;
729 rv = entries->GetNext(entryFilename);
730 NS_ENSURE_SUCCESS(rv, rv);
732 PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("Verifying digests for %s",
733 entryFilename.get()));
735 // The files that comprise the signature mechanism are not covered by the
736 // signature.
737 //
738 // XXX: This is OK for a single signature, but doesn't work for
739 // multiple signatures, because the metadata for the other signatures
740 // is not signed either.
741 if (entryFilename == mfFilename ||
742 entryFilename == sfFilename ||
743 entryFilename == sigFilename) {
744 continue;
745 }
747 if (entryFilename.Length() == 0) {
748 return NS_ERROR_SIGNED_JAR_ENTRY_INVALID;
749 }
751 // Entries with names that end in "/" are directory entries, which are not
752 // signed.
753 //
754 // XXX: As long as we don't unpack the JAR into the filesystem, the "/"
755 // entries are harmless. But, it is not clear what the security
756 // implications of directory entries are if/when we were to unpackage the
757 // JAR into the filesystem.
758 if (entryFilename[entryFilename.Length() - 1] == '/') {
759 continue;
760 }
762 nsCStringHashKey * item = items.GetEntry(entryFilename);
763 if (!item) {
764 return NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY;
765 }
767 // Remove the item so we can check for leftover items later
768 items.RemoveEntry(entryFilename);
769 }
771 // We verified that every entry that we require to be signed is signed. But,
772 // were there any missing entries--that is, entries that are mentioned in the
773 // manifest but missing from the archive?
774 if (items.Count() != 0) {
775 return NS_ERROR_SIGNED_JAR_ENTRY_MISSING;
776 }
778 // Return the reader to the caller if they want it
779 if (aZipReader) {
780 zip.forget(aZipReader);
781 }
783 // Return the signer's certificate to the reader if they want it.
784 // XXX: We should return an nsIX509CertList with the whole validated chain,
785 // but we can't do that until we switch to libpkix.
786 if (aSignerCert) {
787 MOZ_ASSERT(CERT_LIST_HEAD(builtChain));
788 nsCOMPtr<nsIX509Cert3> signerCert =
789 nsNSSCertificate::Create(CERT_LIST_HEAD(builtChain)->cert);
790 NS_ENSURE_TRUE(signerCert, NS_ERROR_OUT_OF_MEMORY);
791 signerCert.forget(aSignerCert);
792 }
794 return NS_OK;
795 }
797 class OpenSignedAppFileTask MOZ_FINAL : public CryptoTask
798 {
799 public:
800 OpenSignedAppFileTask(AppTrustedRoot aTrustedRoot, nsIFile* aJarFile,
801 nsIOpenSignedAppFileCallback* aCallback)
802 : mTrustedRoot(aTrustedRoot)
803 , mJarFile(aJarFile)
804 , mCallback(new nsMainThreadPtrHolder<nsIOpenSignedAppFileCallback>(aCallback))
805 {
806 }
808 private:
809 virtual nsresult CalculateResult() MOZ_OVERRIDE
810 {
811 return OpenSignedAppFile(mTrustedRoot, mJarFile,
812 getter_AddRefs(mZipReader),
813 getter_AddRefs(mSignerCert));
814 }
816 // nsNSSCertificate implements nsNSSShutdownObject, so there's nothing that
817 // needs to be released
818 virtual void ReleaseNSSResources() { }
820 virtual void CallCallback(nsresult rv)
821 {
822 (void) mCallback->OpenSignedAppFileFinished(rv, mZipReader, mSignerCert);
823 }
825 const AppTrustedRoot mTrustedRoot;
826 const nsCOMPtr<nsIFile> mJarFile;
827 nsMainThreadPtrHandle<nsIOpenSignedAppFileCallback> mCallback;
828 nsCOMPtr<nsIZipReader> mZipReader; // out
829 nsCOMPtr<nsIX509Cert3> mSignerCert; // out
830 };
832 } // unnamed namespace
834 NS_IMETHODIMP
835 nsNSSCertificateDB::OpenSignedAppFileAsync(
836 AppTrustedRoot aTrustedRoot, nsIFile* aJarFile,
837 nsIOpenSignedAppFileCallback* aCallback)
838 {
839 NS_ENSURE_ARG_POINTER(aJarFile);
840 NS_ENSURE_ARG_POINTER(aCallback);
841 RefPtr<OpenSignedAppFileTask> task(new OpenSignedAppFileTask(aTrustedRoot,
842 aJarFile,
843 aCallback));
844 return task->Dispatch("SignedJAR");
845 }