Bumping manifests a=b2g-bump
[gecko.git] / security / apps / AppSignatureVerification.cpp
blobc11e5506cd1573891e8f2fa193b28efae193b849
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 "pkix/pkix.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"
16 #include "nsCOMPtr.h"
17 #include "nsDataSignatureVerifier.h"
18 #include "nsHashKeys.h"
19 #include "nsIFile.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"
28 #include "nsString.h"
29 #include "nsTHashtable.h"
31 #include "base64.h"
32 #include "certdb.h"
33 #include "nssb64.h"
34 #include "secmime.h"
35 #include "plstr.h"
36 #include "prlog.h"
38 using namespace mozilla::pkix;
39 using namespace mozilla;
40 using namespace mozilla::psm;
42 #ifdef PR_LOGGING
43 extern PRLogModuleInfo* gPIPNSSLog;
44 #endif
46 namespace {
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.
58 nsresult
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
63 // the file.
64 uint64_t length;
65 nsresult rv = stream->Available(&length);
66 if (NS_WARN_IF(NS_FAILED(rv))) {
67 return 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
81 // the buffer.
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.
87 uint32_t bytesRead;
88 rv = stream->Read(char_ptr_cast(buf.data), buf.len, &bytesRead);
89 if (NS_WARN_IF(NS_FAILED(rv))) {
90 return rv;
92 if (bytesRead != length) {
93 return NS_ERROR_FILE_CORRUPTED;
96 buf.data[buf.len - 1] = 0; // null-terminate
98 return NS_OK;
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.
105 nsresult
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;
118 bool more;
119 rv = files->HasMore(&more);
120 NS_ENSURE_SUCCESS(rv, rv);
121 if (!more) {
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);
131 if (more) {
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;
144 if (bufDigest) {
145 rv = bufDigest->DigestBuf(SEC_OID_SHA1, buf.data, buf.len - 1);
146 NS_ENSURE_SUCCESS(rv, rv);
149 return NS_OK;
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
160 // size of our I/O.
161 nsresult
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;
169 nsresult rv;
171 nsCOMPtr<nsIInputStream> stream;
172 rv = zip->GetInputStream(aFilename, getter_AddRefs(stream));
173 if (NS_FAILED(rv)) {
174 return NS_ERROR_SIGNED_JAR_ENTRY_MISSING;
177 uint64_t len64;
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;
193 for (;;) {
194 uint32_t bytesRead;
195 rv = stream->Read(char_ptr_cast(buf.data), buf.len, &bytesRead);
196 NS_ENSURE_SUCCESS(rv, rv);
198 if (bytesRead == 0) {
199 break; // EOF
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
213 // the entry.
214 return NS_ERROR_SIGNED_JAR_ENTRY_INVALID;
217 // Verify that the digests match.
218 Digest digest;
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;
226 return NS_OK;
229 // On input, nextLineStart is the start of the current line. On output,
230 // nextLineStart is the start of the next line.
231 nsresult
232 ReadLine(/*in/out*/ const char* & nextLineStart, /*out*/ nsCString & line,
233 bool allowContinuations = true)
235 line.Truncate();
236 size_t previousLength = 0;
237 size_t currentLength = 0;
238 for (;;) {
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;
262 if (*eol == '\r') {
263 ++eol;
265 if (*eol == '\n') {
266 ++eol;
269 nextLineStart = eol;
271 if (*eol != ' ') {
272 // not a continuation
273 return NS_OK;
276 // 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"
292 nsresult
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;
305 for (;;) {
306 if (nameEnd == 0) {
307 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; // colon with no name
309 if (curLine[nameEnd - 1] != ' ')
310 break;
311 --nameEnd;
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] == ' ') {
320 ++valueStart;
322 curLine.Right(attrValue, curLineLength - valueStart);
324 return NS_OK;
327 // Parses the version line of the MF or SF header.
328 nsresult
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
334 // strings)."
335 nsAutoCString curLine;
336 nsresult rv = ReadLine(nextLineStart, curLine, false);
337 if (NS_FAILED(rv)) {
338 return rv;
340 if (!curLine.Equals(expectedHeader)) {
341 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
343 return NS_OK;
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
364 // algorithms.
366 // filebuf must be null-terminated. On output, mfDigest will contain the
367 // decoded value of SHA1-Digest-Manifest.
368 nsresult
369 ParseSF(const char* filebuf, /*out*/ SECItem & mfDigest)
371 nsresult rv;
373 const char* nextLineStart = filebuf;
374 rv = CheckManifestVersion(nextLineStart, NS_LITERAL_CSTRING(JAR_SF_HEADER));
375 if (NS_FAILED(rv))
376 return rv;
378 // Find SHA1-Digest-Manifest
379 for (;;) {
380 nsAutoCString curLine;
381 rv = ReadLine(nextLineStart, curLine);
382 if (NS_FAILED(rv)) {
383 return rv;
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);
395 if (NS_FAILED(rv)) {
396 return rv;
399 if (attrName.LowerCaseEqualsLiteral("sha1-digest-manifest")) {
400 rv = MapSECStatus(ATOB_ConvertAsciiToItem(&mfDigest, attrValue.get()));
401 if (NS_FAILED(rv)) {
402 return rv;
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
412 // attributes.
413 break;
416 // ignore unrecognized attributes
419 return NS_OK;
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
424 // I/O.
425 nsresult
426 ParseMF(const char* filebuf, nsIZipReader * zip,
427 /*out*/ nsTHashtable<nsCStringHashKey> & mfItems,
428 ScopedAutoSECItem & buf)
430 nsresult rv;
432 const char* nextLineStart = filebuf;
434 rv = CheckManifestVersion(nextLineStart, NS_LITERAL_CSTRING(JAR_MF_HEADER));
435 if (NS_FAILED(rv)) {
436 return rv;
439 // Skip the rest of the header section, which ends with a blank line.
441 nsAutoCString line;
442 do {
443 rv = ReadLine(nextLineStart, line);
444 if (NS_FAILED(rv)) {
445 return rv;
447 } while (line.Length() > 0);
449 // Manifest containing no file entries is OK, though useless.
450 if (*nextLineStart == '\0') {
451 return NS_OK;
455 nsAutoCString curItemName;
456 ScopedAutoSECItem digest;
458 for (;;) {
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)) {
479 // Duplicate entry
480 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
483 // Verify that the entry's content digest matches the digest from this
484 // MF section.
485 rv = VerifyEntryContentDigest(zip, curItemName, digest, buf);
486 if (NS_FAILED(rv))
487 return rv;
489 mfItems.PutEntry(curItemName);
491 if (*nextLineStart == '\0') // end-of-file
492 break;
494 // reset so we know we haven't encountered either of these for the next
495 // item yet.
496 curItemName.Truncate();
497 digest.reset();
499 continue; // skip the rest of the loop below
502 nsAutoCString attrName;
503 nsAutoCString attrValue;
504 rv = ParseAttribute(curLine, attrName, attrValue);
505 if (NS_FAILED(rv)) {
506 return rv;
509 // Lines to look for:
511 // (1) Digest:
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()));
518 if (NS_FAILED(rv))
519 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
521 continue;
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;
535 continue;
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
549 return NS_OK;
552 struct VerifyCertificateContext {
553 AppTrustedRoot trustedRoot;
554 ScopedCERTCertList& builtChain;
557 nsresult
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);
571 Input certDER;
572 Result rv = certDER.Init(signerCert->derCert.data, signerCert->derCert.len);
573 if (rv != Success) {
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*/);
583 if (rv != Success) {
584 return mozilla::psm::GetXPCOMFromNSSError(MapResultToPRErrorCode(rv));
587 return NS_OK;
590 nsresult
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,
598 VerifyCertificate,
599 &context, nullptr);
602 NS_IMETHODIMP
603 OpenSignedAppFile(AppTrustedRoot aTrustedRoot, nsIFile* aJarFile,
604 /*out, optional */ nsIZipReader** aZipReader,
605 /*out, optional */ nsIX509Cert** aSignerCert)
607 NS_ENSURE_ARG_POINTER(aJarFile);
609 if (aZipReader) {
610 *aZipReader = nullptr;
613 if (aSignerCert) {
614 *aSignerCert = nullptr;
617 nsresult rv;
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);
631 if (NS_FAILED(rv)) {
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);
641 if (NS_FAILED(rv)) {
642 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
645 sigBuffer.type = siBuffer;
646 ScopedCERTCertList builtChain;
647 rv = VerifySignature(aTrustedRoot, sigBuffer, sfCalculatedDigest.get(),
648 builtChain);
649 if (NS_FAILED(rv)) {
650 return rv;
653 ScopedAutoSECItem mfDigest;
654 rv = ParseSF(char_ptr_cast(sfBuffer.data), mfDigest);
655 if (NS_FAILED(rv)) {
656 return rv;
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);
665 if (NS_FAILED(rv)) {
666 return rv;
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
675 // memory.
676 ScopedAutoSECItem buf(128 * 1024);
678 nsTHashtable<nsCStringHashKey> items;
680 rv = ParseMF(char_ptr_cast(manifestBuffer.data), zip, items, buf);
681 if (NS_FAILED(rv)) {
682 return rv;
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;
691 if (NS_FAILED(rv)) {
692 return rv;
695 for (;;) {
696 bool hasMore;
697 rv = entries->HasMore(&hasMore);
698 NS_ENSURE_SUCCESS(rv, rv);
700 if (!hasMore) {
701 break;
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
712 // signature.
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) {
720 continue;
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
728 // signed.
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] == '/') {
735 continue;
738 nsCStringHashKey * item = items.GetEntry(entryFilename);
739 if (!item) {
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
755 if (aZipReader) {
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.
761 if (aSignerCert) {
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);
769 return NS_OK;
772 nsresult
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);
781 if (aSignerCert) {
782 *aSignerCert = nullptr;
785 // Load signature file in buffer
786 ScopedAutoSECItem signatureBuffer;
787 nsresult rv = ReadStream(aSignatureStream, signatureBuffer);
788 if (NS_FAILED(rv)) {
789 return rv;
791 signatureBuffer.type = siBuffer;
793 // Load manifest file in buffer
794 ScopedAutoSECItem manifestBuffer;
795 rv = ReadStream(aManifestStream, manifestBuffer);
796 if (NS_FAILED(rv)) {
797 return rv;
800 // Calculate SHA1 digest of the manifest buffer
801 Digest manifestCalculatedDigest;
802 rv = manifestCalculatedDigest.DigestBuf(SEC_OID_SHA1,
803 manifestBuffer.data,
804 manifestBuffer.len - 1); // buffer is null terminated
805 if (NS_WARN_IF(NS_FAILED(rv))) {
806 return 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
817 Digest doubleDigest;
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))) {
822 return 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);
829 if (NS_FAILED(rv)) {
830 return rv;
833 // Return the signer's certificate to the reader if they want it.
834 if (aSignerCert) {
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);
845 return NS_OK;
848 class OpenSignedAppFileTask MOZ_FINAL : public CryptoTask
850 public:
851 OpenSignedAppFileTask(AppTrustedRoot aTrustedRoot, nsIFile* aJarFile,
852 nsIOpenSignedAppFileCallback* aCallback)
853 : mTrustedRoot(aTrustedRoot)
854 , mJarFile(aJarFile)
855 , mCallback(new nsMainThreadPtrHolder<nsIOpenSignedAppFileCallback>(aCallback))
859 private:
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
885 public:
886 VerifySignedmanifestTask(AppTrustedRoot aTrustedRoot,
887 nsIInputStream* aManifestStream,
888 nsIInputStream* aSignatureStream,
889 nsIVerifySignedManifestCallback* aCallback)
890 : mTrustedRoot(aTrustedRoot)
891 , mManifestStream(aManifestStream)
892 , mSignatureStream(aSignatureStream)
893 , mCallback(
894 new nsMainThreadPtrHolder<nsIVerifySignedManifestCallback>(aCallback))
898 private:
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
923 NS_IMETHODIMP
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,
931 aJarFile,
932 aCallback));
933 return task->Dispatch("SignedJAR");
936 NS_IMETHODIMP
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");