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/. */
6
7 #include "nsNSSCertificateDB.h"
8
9 #include "AppTrustDomain.h"
10 #include "CryptoTask.h"
11 #include "NSSCertDBTrustDomain.h"
12 #include "ScopedNSSTypes.h"
13 #include "base64.h"
14 #include "certdb.h"
15 #include "mozilla/Casting.h"
16 #include "mozilla/Logging.h"
17 #include "mozilla/RefPtr.h"
18 #include "mozilla/UniquePtr.h"
19 #include "nsCOMPtr.h"
20 #include "nsComponentManagerUtils.h"
21 #include "nsDataSignatureVerifier.h"
22 #include "nsHashKeys.h"
23 #include "nsIDirectoryEnumerator.h"
24 #include "nsIFile.h"
25 #include "nsIFileStreams.h"
26 #include "nsIInputStream.h"
27 #include "nsIStringEnumerator.h"
28 #include "nsIZipReader.h"
29 #include "nsNSSCertificate.h"
30 #include "nsNetUtil.h"
31 #include "nsProxyRelease.h"
32 #include "nsString.h"
33 #include "nsTHashtable.h"
34 #include "nssb64.h"
35 #include "pkix/pkix.h"
36 #include "pkix/pkixnss.h"
37 #include "plstr.h"
38 #include "secmime.h"
39
40
41 using namespace mozilla::pkix;
42 using namespace mozilla;
43 using namespace mozilla::psm;
44
45 extern mozilla::LazyLogModule gPIPNSSLog;
46
47 namespace {
48
49 // Reads a maximum of 1MB from a stream into the supplied buffer.
50 // The reason for the 1MB limit is because this function is used to read
51 // signature-related files and we want to avoid OOM. The uncompressed length of
52 // an entry can be hundreds of times larger than the compressed version,
53 // especially if someone has specifically crafted the entry to cause OOM or to
54 // consume massive amounts of disk space.
55 //
56 // @param stream The input stream to read from.
57 // @param buf The buffer that we read the stream into, which must have
58 // already been allocated.
59 nsresult
ReadStream(const nsCOMPtr<nsIInputStream> & stream,SECItem & buf)60 ReadStream(const nsCOMPtr<nsIInputStream>& stream, /*out*/ SECItem& buf)
61 {
62 // The size returned by Available() might be inaccurate so we need
63 // to check that Available() matches up with the actual length of
64 // the file.
65 uint64_t length;
66 nsresult rv = stream->Available(&length);
67 if (NS_WARN_IF(NS_FAILED(rv))) {
68 return rv;
69 }
70
71 // Cap the maximum accepted size of signature-related files at 1MB (which is
72 // still crazily huge) to avoid OOM. The uncompressed length of an entry can be
73 // hundreds of times larger than the compressed version, especially if
74 // someone has speifically crafted the entry to cause OOM or to consume
75 // massive amounts of disk space.
76 static const uint32_t MAX_LENGTH = 1024 * 1024;
77 if (length > MAX_LENGTH) {
78 return NS_ERROR_FILE_TOO_BIG;
79 }
80
81 // With bug 164695 in mind we +1 to leave room for null-terminating
82 // the buffer.
83 SECITEM_AllocItem(buf, static_cast<uint32_t>(length + 1));
84
85 // buf.len == length + 1. We attempt to read length + 1 bytes
86 // instead of length, so that we can check whether the metadata for
87 // the entry is incorrect.
88 uint32_t bytesRead;
89 rv = stream->Read(BitwiseCast<char*, unsigned char*>(buf.data), buf.len,
90 &bytesRead);
91 if (NS_WARN_IF(NS_FAILED(rv))) {
92 return rv;
93 }
94 if (bytesRead != length) {
95 return NS_ERROR_FILE_CORRUPTED;
96 }
97
98 buf.data[buf.len - 1] = 0; // null-terminate
99
100 return NS_OK;
101 }
102
103 // Finds exactly one (signature metadata) JAR entry that matches the given
104 // search pattern, and then load it. Fails if there are no matches or if
105 // there is more than one match. If bugDigest is not null then on success
106 // bufDigest will contain the SHA-1 digeset of the entry.
107 nsresult
FindAndLoadOneEntry(nsIZipReader * zip,const nsACString & searchPattern,nsACString & filename,SECItem & buf,Digest * bufDigest)108 FindAndLoadOneEntry(nsIZipReader * zip,
109 const nsACString & searchPattern,
110 /*out*/ nsACString & filename,
111 /*out*/ SECItem & buf,
112 /*optional, out*/ Digest * bufDigest)
113 {
114 nsCOMPtr<nsIUTF8StringEnumerator> files;
115 nsresult rv = zip->FindEntries(searchPattern, getter_AddRefs(files));
116 if (NS_FAILED(rv) || !files) {
117 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
118 }
119
120 bool more;
121 rv = files->HasMore(&more);
122 NS_ENSURE_SUCCESS(rv, rv);
123 if (!more) {
124 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
125 }
126
127 rv = files->GetNext(filename);
128 NS_ENSURE_SUCCESS(rv, rv);
129
130 // Check if there is more than one match, if so then error!
131 rv = files->HasMore(&more);
132 NS_ENSURE_SUCCESS(rv, rv);
133 if (more) {
134 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
135 }
136
137 nsCOMPtr<nsIInputStream> stream;
138 rv = zip->GetInputStream(filename, getter_AddRefs(stream));
139 NS_ENSURE_SUCCESS(rv, rv);
140
141 rv = ReadStream(stream, buf);
142 if (NS_WARN_IF(NS_FAILED(rv))) {
143 return NS_ERROR_SIGNED_JAR_ENTRY_INVALID;
144 }
145
146 if (bufDigest) {
147 rv = bufDigest->DigestBuf(SEC_OID_SHA1, buf.data, buf.len - 1);
148 NS_ENSURE_SUCCESS(rv, rv);
149 }
150
151 return NS_OK;
152 }
153
154 // Verify the digest of an entry. We avoid loading the entire entry into memory
155 // at once, which would require memory in proportion to the size of the largest
156 // entry. Instead, we require only a small, fixed amount of memory.
157 //
158 // @param stream an input stream from a JAR entry or file depending on whether
159 // it is from a signed archive or unpacked into a directory
160 // @param digestFromManifest The digest that we're supposed to check the file's
161 // contents against, from the manifest
162 // @param buf A scratch buffer that we use for doing the I/O, which must have
163 // already been allocated. The size of this buffer is the unit
164 // size of our I/O.
165 nsresult
VerifyStreamContentDigest(nsIInputStream * stream,const SECItem & digestFromManifest,SECItem & buf)166 VerifyStreamContentDigest(nsIInputStream* stream,
167 const SECItem& digestFromManifest, SECItem& buf)
168 {
169 MOZ_ASSERT(buf.len > 0);
170 if (digestFromManifest.len != SHA1_LENGTH)
171 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
172
173 nsresult rv;
174 uint64_t len64;
175 rv = stream->Available(&len64);
176 NS_ENSURE_SUCCESS(rv, rv);
177 if (len64 > UINT32_MAX) {
178 return NS_ERROR_SIGNED_JAR_ENTRY_TOO_LARGE;
179 }
180
181 UniquePK11Context digestContext(PK11_CreateDigestContext(SEC_OID_SHA1));
182 if (!digestContext) {
183 return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
184 }
185
186 rv = MapSECStatus(PK11_DigestBegin(digestContext.get()));
187 NS_ENSURE_SUCCESS(rv, rv);
188
189 uint64_t totalBytesRead = 0;
190 for (;;) {
191 uint32_t bytesRead;
192 rv = stream->Read(BitwiseCast<char*, unsigned char*>(buf.data), buf.len,
193 &bytesRead);
194 NS_ENSURE_SUCCESS(rv, rv);
195
196 if (bytesRead == 0) {
197 break; // EOF
198 }
199
200 totalBytesRead += bytesRead;
201 if (totalBytesRead >= UINT32_MAX) {
202 return NS_ERROR_SIGNED_JAR_ENTRY_TOO_LARGE;
203 }
204
205 rv = MapSECStatus(PK11_DigestOp(digestContext.get(), buf.data, bytesRead));
206 NS_ENSURE_SUCCESS(rv, rv);
207 }
208
209 if (totalBytesRead != len64) {
210 // The metadata we used for Available() doesn't match the actual size of
211 // the entry.
212 return NS_ERROR_SIGNED_JAR_ENTRY_INVALID;
213 }
214
215 // Verify that the digests match.
216 Digest digest;
217 rv = digest.End(SEC_OID_SHA1, digestContext);
218 NS_ENSURE_SUCCESS(rv, rv);
219
220 if (SECITEM_CompareItem(&digestFromManifest, &digest.get()) != SECEqual) {
221 return NS_ERROR_SIGNED_JAR_MODIFIED_ENTRY;
222 }
223
224 return NS_OK;
225 }
226
227 nsresult
VerifyEntryContentDigest(nsIZipReader * zip,const nsACString & aFilename,const SECItem & digestFromManifest,SECItem & buf)228 VerifyEntryContentDigest(nsIZipReader* zip, const nsACString& aFilename,
229 const SECItem& digestFromManifest, SECItem& buf)
230 {
231 nsCOMPtr<nsIInputStream> stream;
232 nsresult rv = zip->GetInputStream(aFilename, getter_AddRefs(stream));
233 if (NS_FAILED(rv)) {
234 return NS_ERROR_SIGNED_JAR_ENTRY_MISSING;
235 }
236
237 return VerifyStreamContentDigest(stream, digestFromManifest, buf);
238 }
239
240 // @oaram aDir directory containing the unpacked signed archive
241 // @param aFilename path of the target file relative to aDir
242 // @param digestFromManifest The digest that we're supposed to check the file's
243 // contents against, from the manifest
244 // @param buf A scratch buffer that we use for doing the I/O
245 nsresult
VerifyFileContentDigest(nsIFile * aDir,const nsAString & aFilename,const SECItem & digestFromManifest,SECItem & buf)246 VerifyFileContentDigest(nsIFile* aDir, const nsAString& aFilename,
247 const SECItem& digestFromManifest, SECItem& buf)
248 {
249 // Find the file corresponding to the manifest path
250 nsCOMPtr<nsIFile> file;
251 nsresult rv = aDir->Clone(getter_AddRefs(file));
252 if (NS_FAILED(rv)) {
253 return rv;
254 }
255
256 // We don't know how to handle JARs with signed directory entries.
257 // It's technically possible in the manifest but makes no sense on disk.
258 // Inside an archive we just ignore them, but here we have to treat it
259 // as an error because the signed bytes never got unpacked.
260 int32_t pos = 0;
261 int32_t slash;
262 int32_t namelen = aFilename.Length();
263 if (namelen == 0 || aFilename[namelen - 1] == '/') {
264 return NS_ERROR_SIGNED_JAR_ENTRY_INVALID;
265 }
266
267 // Append path segments one by one
268 do {
269 slash = aFilename.FindChar('/', pos);
270 int32_t segend = (slash == kNotFound) ? namelen : slash;
271 rv = file->Append(Substring(aFilename, pos, (segend - pos)));
272 if (NS_FAILED(rv)) {
273 return NS_ERROR_SIGNED_JAR_ENTRY_INVALID;
274 }
275 pos = slash + 1;
276 } while (pos < namelen && slash != kNotFound);
277
278 bool exists;
279 rv = file->Exists(&exists);
280 if (NS_FAILED(rv) || !exists) {
281 return NS_ERROR_SIGNED_JAR_ENTRY_MISSING;
282 }
283
284 bool isDir;
285 rv = file->IsDirectory(&isDir);
286 if (NS_FAILED(rv) || isDir) {
287 // We only support signed files, not directory entries
288 return NS_ERROR_SIGNED_JAR_ENTRY_INVALID;
289 }
290
291 // Open an input stream for that file and verify it.
292 nsCOMPtr<nsIInputStream> stream;
293 rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), file, -1, -1,
294 nsIFileInputStream::CLOSE_ON_EOF);
295 if (NS_FAILED(rv) || !stream) {
296 return NS_ERROR_SIGNED_JAR_ENTRY_MISSING;
297 }
298
299 return VerifyStreamContentDigest(stream, digestFromManifest, buf);
300 }
301
302 // On input, nextLineStart is the start of the current line. On output,
303 // nextLineStart is the start of the next line.
304 nsresult
ReadLine(const char * & nextLineStart,nsCString & line,bool allowContinuations=true)305 ReadLine(/*in/out*/ const char* & nextLineStart, /*out*/ nsCString & line,
306 bool allowContinuations = true)
307 {
308 line.Truncate();
309 size_t previousLength = 0;
310 size_t currentLength = 0;
311 for (;;) {
312 const char* eol = PL_strpbrk(nextLineStart, "\r\n");
313
314 if (!eol) { // Reached end of file before newline
315 eol = nextLineStart + strlen(nextLineStart);
316 }
317
318 previousLength = currentLength;
319 line.Append(nextLineStart, eol - nextLineStart);
320 currentLength = line.Length();
321
322 // The spec says "No line may be longer than 72 bytes (not characters)"
323 // in its UTF8-encoded form.
324 static const size_t lineLimit = 72;
325 if (currentLength - previousLength > lineLimit) {
326 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
327 }
328
329 // The spec says: "Implementations should support 65535-byte
330 // (not character) header values..."
331 if (currentLength > 65535) {
332 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
333 }
334
335 if (*eol == '\r') {
336 ++eol;
337 }
338 if (*eol == '\n') {
339 ++eol;
340 }
341
342 nextLineStart = eol;
343
344 if (*eol != ' ') {
345 // not a continuation
346 return NS_OK;
347 }
348
349 // continuation
350 if (!allowContinuations) {
351 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
352 }
353
354 ++nextLineStart; // skip space and keep appending
355 }
356 }
357
358 // The header strings are defined in the JAR specification.
359 #define JAR_MF_SEARCH_STRING "(M|/M)ETA-INF/(M|m)(ANIFEST|anifest).(MF|mf)$"
360 #define JAR_SF_SEARCH_STRING "(M|/M)ETA-INF/*.(SF|sf)$"
361 #define JAR_RSA_SEARCH_STRING "(M|/M)ETA-INF/*.(RSA|rsa)$"
362 #define JAR_META_DIR "META-INF"
363 #define JAR_MF_HEADER "Manifest-Version: 1.0"
364 #define JAR_SF_HEADER "Signature-Version: 1.0"
365
366 nsresult
ParseAttribute(const nsAutoCString & curLine,nsAutoCString & attrName,nsAutoCString & attrValue)367 ParseAttribute(const nsAutoCString & curLine,
368 /*out*/ nsAutoCString & attrName,
369 /*out*/ nsAutoCString & attrValue)
370 {
371 // Find the colon that separates the name from the value.
372 int32_t colonPos = curLine.FindChar(':');
373 if (colonPos == kNotFound) {
374 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
375 }
376
377 // set attrName to the name, skipping spaces between the name and colon
378 int32_t nameEnd = colonPos;
379 for (;;) {
380 if (nameEnd == 0) {
381 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; // colon with no name
382 }
383 if (curLine[nameEnd - 1] != ' ')
384 break;
385 --nameEnd;
386 }
387 curLine.Left(attrName, nameEnd);
388
389 // Set attrValue to the value, skipping spaces between the colon and the
390 // value. The value may be empty.
391 int32_t valueStart = colonPos + 1;
392 int32_t curLineLength = curLine.Length();
393 while (valueStart != curLineLength && curLine[valueStart] == ' ') {
394 ++valueStart;
395 }
396 curLine.Right(attrValue, curLineLength - valueStart);
397
398 return NS_OK;
399 }
400
401 // Parses the version line of the MF or SF header.
402 nsresult
CheckManifestVersion(const char * & nextLineStart,const nsACString & expectedHeader)403 CheckManifestVersion(const char* & nextLineStart,
404 const nsACString & expectedHeader)
405 {
406 // The JAR spec says: "Manifest-Version and Signature-Version must be first,
407 // and in exactly that case (so that they can be recognized easily as magic
408 // strings)."
409 nsAutoCString curLine;
410 nsresult rv = ReadLine(nextLineStart, curLine, false);
411 if (NS_FAILED(rv)) {
412 return rv;
413 }
414 if (!curLine.Equals(expectedHeader)) {
415 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
416 }
417 return NS_OK;
418 }
419
420 // Parses a signature file (SF) as defined in the JDK 8 JAR Specification.
421 //
422 // The SF file *must* contain exactly one SHA1-Digest-Manifest attribute in
423 // the main section. All other sections are ignored. This means that this will
424 // NOT parse old-style signature files that have separate digests per entry.
425 // The JDK8 x-Digest-Manifest variant is better because:
426 //
427 // (1) It allows us to follow the principle that we should minimize the
428 // processing of data that we do before we verify its signature. In
429 // particular, with the x-Digest-Manifest style, we can verify the digest
430 // of MANIFEST.MF before we parse it, which prevents malicious JARs
431 // exploiting our MANIFEST.MF parser.
432 // (2) It is more time-efficient and space-efficient to have one
433 // x-Digest-Manifest instead of multiple x-Digest values.
434 //
435 // In order to get benefit (1), we do NOT implement the fallback to the older
436 // mechanism as the spec requires/suggests. Also, for simplity's sake, we only
437 // support exactly one SHA1-Digest-Manifest attribute, and no other
438 // algorithms.
439 //
440 // filebuf must be null-terminated. On output, mfDigest will contain the
441 // decoded value of SHA1-Digest-Manifest.
442 nsresult
ParseSF(const char * filebuf,SECItem & mfDigest)443 ParseSF(const char* filebuf, /*out*/ SECItem & mfDigest)
444 {
445 nsresult rv;
446
447 const char* nextLineStart = filebuf;
448 rv = CheckManifestVersion(nextLineStart, NS_LITERAL_CSTRING(JAR_SF_HEADER));
449 if (NS_FAILED(rv))
450 return rv;
451
452 // Find SHA1-Digest-Manifest
453 for (;;) {
454 nsAutoCString curLine;
455 rv = ReadLine(nextLineStart, curLine);
456 if (NS_FAILED(rv)) {
457 return rv;
458 }
459
460 if (curLine.Length() == 0) {
461 // End of main section (blank line or end-of-file), and no
462 // SHA1-Digest-Manifest found.
463 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
464 }
465
466 nsAutoCString attrName;
467 nsAutoCString attrValue;
468 rv = ParseAttribute(curLine, attrName, attrValue);
469 if (NS_FAILED(rv)) {
470 return rv;
471 }
472
473 if (attrName.LowerCaseEqualsLiteral("sha1-digest-manifest")) {
474 rv = MapSECStatus(ATOB_ConvertAsciiToItem(&mfDigest, attrValue.get()));
475 if (NS_FAILED(rv)) {
476 return rv;
477 }
478
479 // There could be multiple SHA1-Digest-Manifest attributes, which
480 // would be an error, but it's better to just skip any erroneous
481 // duplicate entries rather than trying to detect them, because:
482 //
483 // (1) It's simpler, and simpler generally means more secure
484 // (2) An attacker can't make us accept a JAR we would otherwise
485 // reject just by adding additional SHA1-Digest-Manifest
486 // attributes.
487 break;
488 }
489
490 // ignore unrecognized attributes
491 }
492
493 return NS_OK;
494 }
495
496 // Parses MANIFEST.MF. The filenames of all entries will be returned in
497 // mfItems. buf must be a pre-allocated scratch buffer that is used for doing
498 // I/O.
499 nsresult
ParseMF(const char * filebuf,nsIZipReader * zip,nsTHashtable<nsCStringHashKey> & mfItems,ScopedAutoSECItem & buf)500 ParseMF(const char* filebuf, nsIZipReader * zip,
501 /*out*/ nsTHashtable<nsCStringHashKey> & mfItems,
502 ScopedAutoSECItem & buf)
503 {
504 nsresult rv;
505
506 const char* nextLineStart = filebuf;
507
508 rv = CheckManifestVersion(nextLineStart, NS_LITERAL_CSTRING(JAR_MF_HEADER));
509 if (NS_FAILED(rv)) {
510 return rv;
511 }
512
513 // Skip the rest of the header section, which ends with a blank line.
514 {
515 nsAutoCString line;
516 do {
517 rv = ReadLine(nextLineStart, line);
518 if (NS_FAILED(rv)) {
519 return rv;
520 }
521 } while (line.Length() > 0);
522
523 // Manifest containing no file entries is OK, though useless.
524 if (*nextLineStart == '\0') {
525 return NS_OK;
526 }
527 }
528
529 nsAutoCString curItemName;
530 ScopedAutoSECItem digest;
531
532 for (;;) {
533 nsAutoCString curLine;
534 rv = ReadLine(nextLineStart, curLine);
535 NS_ENSURE_SUCCESS(rv, rv);
536
537 if (curLine.Length() == 0) {
538 // end of section (blank line or end-of-file)
539
540 if (curItemName.Length() == 0) {
541 // '...Each section must start with an attribute with the name as
542 // "Name",...', so every section must have a Name attribute.
543 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
544 }
545
546 if (digest.len == 0) {
547 // We require every entry to have a digest, since we require every
548 // entry to be signed and we don't allow duplicate entries.
549 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
550 }
551
552 if (mfItems.Contains(curItemName)) {
553 // Duplicate entry
554 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
555 }
556
557 // Verify that the entry's content digest matches the digest from this
558 // MF section.
559 rv = VerifyEntryContentDigest(zip, curItemName, digest, buf);
560 if (NS_FAILED(rv))
561 return rv;
562
563 mfItems.PutEntry(curItemName);
564
565 if (*nextLineStart == '\0') // end-of-file
566 break;
567
568 // reset so we know we haven't encountered either of these for the next
569 // item yet.
570 curItemName.Truncate();
571 digest.reset();
572
573 continue; // skip the rest of the loop below
574 }
575
576 nsAutoCString attrName;
577 nsAutoCString attrValue;
578 rv = ParseAttribute(curLine, attrName, attrValue);
579 if (NS_FAILED(rv)) {
580 return rv;
581 }
582
583 // Lines to look for:
584
585 // (1) Digest:
586 if (attrName.LowerCaseEqualsLiteral("sha1-digest"))
587 {
588 if (digest.len > 0) // multiple SHA1 digests in section
589 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
590
591 rv = MapSECStatus(ATOB_ConvertAsciiToItem(&digest, attrValue.get()));
592 if (NS_FAILED(rv))
593 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
594
595 continue;
596 }
597
598 // (2) Name: associates this manifest section with a file in the jar.
599 if (attrName.LowerCaseEqualsLiteral("name"))
600 {
601 if (MOZ_UNLIKELY(curItemName.Length() > 0)) // multiple names in section
602 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
603
604 if (MOZ_UNLIKELY(attrValue.Length() == 0))
605 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
606
607 curItemName = attrValue;
608
609 continue;
610 }
611
612 // (3) Magic: the only other must-understand attribute
613 if (attrName.LowerCaseEqualsLiteral("magic")) {
614 // We don't understand any magic, so we can't verify an entry that
615 // requires magic. Since we require every entry to have a valid
616 // signature, we have no choice but to reject the entry.
617 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
618 }
619
620 // unrecognized attributes must be ignored
621 }
622
623 return NS_OK;
624 }
625
626 struct VerifyCertificateContext {
627 AppTrustedRoot trustedRoot;
628 UniqueCERTCertList& builtChain;
629 };
630
631 nsresult
VerifyCertificate(CERTCertificate * signerCert,void * voidContext,void * pinArg)632 VerifyCertificate(CERTCertificate* signerCert, void* voidContext, void* pinArg)
633 {
634 // TODO: null pinArg is tolerated.
635 if (NS_WARN_IF(!signerCert) || NS_WARN_IF(!voidContext)) {
636 return NS_ERROR_INVALID_ARG;
637 }
638 const VerifyCertificateContext& context =
639 *static_cast<const VerifyCertificateContext*>(voidContext);
640
641 AppTrustDomain trustDomain(context.builtChain, pinArg);
642 if (trustDomain.SetTrustedRoot(context.trustedRoot) != SECSuccess) {
643 return MapSECStatus(SECFailure);
644 }
645 Input certDER;
646 mozilla::pkix::Result rv = certDER.Init(signerCert->derCert.data,
647 signerCert->derCert.len);
648 if (rv != Success) {
649 return mozilla::psm::GetXPCOMFromNSSError(MapResultToPRErrorCode(rv));
650 }
651
652 rv = BuildCertChain(trustDomain, certDER, Now(),
653 EndEntityOrCA::MustBeEndEntity,
654 KeyUsage::digitalSignature,
655 KeyPurposeId::id_kp_codeSigning,
656 CertPolicyId::anyPolicy,
657 nullptr/*stapledOCSPResponse*/);
658 if (rv == mozilla::pkix::Result::ERROR_EXPIRED_CERTIFICATE) {
659 // For code-signing you normally need trusted 3rd-party timestamps to
660 // handle expiration properly. The signer could always mess with their
661 // system clock so you can't trust the certificate was un-expired when
662 // the signing took place. The choice is either to ignore expiration
663 // or to enforce expiration at time of use. The latter leads to the
664 // user-hostile result that perfectly good code stops working.
665 //
666 // Our package format doesn't support timestamps (nor do we have a
667 // trusted 3rd party timestamper), but since we sign all of our apps and
668 // add-ons ourselves we can trust ourselves not to mess with the clock
669 // on the signing systems. We also have a revocation mechanism if we
670 // need it. It's OK to ignore cert expiration under these conditions.
671 //
672 // This is an invalid approach if
673 // * we issue certs to let others sign their own packages
674 // * mozilla::pkix returns "expired" when there are "worse" problems
675 // with the certificate or chain.
676 // (see bug 1267318)
677 rv = Success;
678 }
679 if (rv != Success) {
680 return mozilla::psm::GetXPCOMFromNSSError(MapResultToPRErrorCode(rv));
681 }
682
683 return NS_OK;
684 }
685
686 nsresult
VerifySignature(AppTrustedRoot trustedRoot,const SECItem & buffer,const SECItem & detachedDigest,UniqueCERTCertList & builtChain)687 VerifySignature(AppTrustedRoot trustedRoot, const SECItem& buffer,
688 const SECItem& detachedDigest,
689 /*out*/ UniqueCERTCertList& builtChain)
690 {
691 // Currently, this function is only called within the CalculateResult() method
692 // of CryptoTasks. As such, NSS should not be shut down at this point and the
693 // CryptoTask implementation should already hold a nsNSSShutDownPreventionLock.
694 // We acquire a nsNSSShutDownPreventionLock here solely to prove we did to
695 // VerifyCMSDetachedSignatureIncludingCertificate().
696 nsNSSShutDownPreventionLock locker;
697 VerifyCertificateContext context = { trustedRoot, builtChain };
698 // XXX: missing pinArg
699 return VerifyCMSDetachedSignatureIncludingCertificate(buffer, detachedDigest,
700 VerifyCertificate,
701 &context, nullptr,
702 locker);
703 }
704
705 NS_IMETHODIMP
OpenSignedAppFile(AppTrustedRoot aTrustedRoot,nsIFile * aJarFile,nsIZipReader ** aZipReader,nsIX509Cert ** aSignerCert)706 OpenSignedAppFile(AppTrustedRoot aTrustedRoot, nsIFile* aJarFile,
707 /*out, optional */ nsIZipReader** aZipReader,
708 /*out, optional */ nsIX509Cert** aSignerCert)
709 {
710 NS_ENSURE_ARG_POINTER(aJarFile);
711
712 if (aZipReader) {
713 *aZipReader = nullptr;
714 }
715
716 if (aSignerCert) {
717 *aSignerCert = nullptr;
718 }
719
720 nsresult rv;
721
722 static NS_DEFINE_CID(kZipReaderCID, NS_ZIPREADER_CID);
723 nsCOMPtr<nsIZipReader> zip = do_CreateInstance(kZipReaderCID, &rv);
724 NS_ENSURE_SUCCESS(rv, rv);
725
726 rv = zip->Open(aJarFile);
727 NS_ENSURE_SUCCESS(rv, rv);
728
729 // Signature (RSA) file
730 nsAutoCString sigFilename;
731 ScopedAutoSECItem sigBuffer;
732 rv = FindAndLoadOneEntry(zip, nsLiteralCString(JAR_RSA_SEARCH_STRING),
733 sigFilename, sigBuffer, nullptr);
734 if (NS_FAILED(rv)) {
735 return NS_ERROR_SIGNED_JAR_NOT_SIGNED;
736 }
737
738 // Signature (SF) file
739 nsAutoCString sfFilename;
740 ScopedAutoSECItem sfBuffer;
741 Digest sfCalculatedDigest;
742 rv = FindAndLoadOneEntry(zip, NS_LITERAL_CSTRING(JAR_SF_SEARCH_STRING),
743 sfFilename, sfBuffer, &sfCalculatedDigest);
744 if (NS_FAILED(rv)) {
745 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
746 }
747
748 sigBuffer.type = siBuffer;
749 UniqueCERTCertList builtChain;
750 rv = VerifySignature(aTrustedRoot, sigBuffer, sfCalculatedDigest.get(),
751 builtChain);
752 if (NS_FAILED(rv)) {
753 return rv;
754 }
755
756 ScopedAutoSECItem mfDigest;
757 rv = ParseSF(BitwiseCast<char*, unsigned char*>(sfBuffer.data), mfDigest);
758 if (NS_FAILED(rv)) {
759 return rv;
760 }
761
762 // Manifest (MF) file
763 nsAutoCString mfFilename;
764 ScopedAutoSECItem manifestBuffer;
765 Digest mfCalculatedDigest;
766 rv = FindAndLoadOneEntry(zip, NS_LITERAL_CSTRING(JAR_MF_SEARCH_STRING),
767 mfFilename, manifestBuffer, &mfCalculatedDigest);
768 if (NS_FAILED(rv)) {
769 return rv;
770 }
771
772 if (SECITEM_CompareItem(&mfDigest, &mfCalculatedDigest.get()) != SECEqual) {
773 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
774 }
775
776 // Allocate the I/O buffer only once per JAR, instead of once per entry, in
777 // order to minimize malloc/free calls and in order to avoid fragmenting
778 // memory.
779 ScopedAutoSECItem buf(128 * 1024);
780
781 nsTHashtable<nsCStringHashKey> items;
782
783 rv = ParseMF(BitwiseCast<char*, unsigned char*>(manifestBuffer.data), zip,
784 items, buf);
785 if (NS_FAILED(rv)) {
786 return rv;
787 }
788
789 // Verify every entry in the file.
790 nsCOMPtr<nsIUTF8StringEnumerator> entries;
791 rv = zip->FindEntries(EmptyCString(), getter_AddRefs(entries));
792 if (NS_SUCCEEDED(rv) && !entries) {
793 rv = NS_ERROR_UNEXPECTED;
794 }
795 if (NS_FAILED(rv)) {
796 return rv;
797 }
798
799 for (;;) {
800 bool hasMore;
801 rv = entries->HasMore(&hasMore);
802 NS_ENSURE_SUCCESS(rv, rv);
803
804 if (!hasMore) {
805 break;
806 }
807
808 nsAutoCString entryFilename;
809 rv = entries->GetNext(entryFilename);
810 NS_ENSURE_SUCCESS(rv, rv);
811
812 MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("Verifying digests for %s",
813 entryFilename.get()));
814
815 // The files that comprise the signature mechanism are not covered by the
816 // signature.
817 //
818 // XXX: This is OK for a single signature, but doesn't work for
819 // multiple signatures, because the metadata for the other signatures
820 // is not signed either.
821 if (entryFilename == mfFilename ||
822 entryFilename == sfFilename ||
823 entryFilename == sigFilename) {
824 continue;
825 }
826
827 if (entryFilename.Length() == 0) {
828 return NS_ERROR_SIGNED_JAR_ENTRY_INVALID;
829 }
830
831 // Entries with names that end in "/" are directory entries, which are not
832 // signed.
833 //
834 // XXX: As long as we don't unpack the JAR into the filesystem, the "/"
835 // entries are harmless. But, it is not clear what the security
836 // implications of directory entries are if/when we were to unpackage the
837 // JAR into the filesystem.
838 if (entryFilename[entryFilename.Length() - 1] == '/') {
839 continue;
840 }
841
842 nsCStringHashKey * item = items.GetEntry(entryFilename);
843 if (!item) {
844 return NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY;
845 }
846
847 // Remove the item so we can check for leftover items later
848 items.RemoveEntry(item);
849 }
850
851 // We verified that every entry that we require to be signed is signed. But,
852 // were there any missing entries--that is, entries that are mentioned in the
853 // manifest but missing from the archive?
854 if (items.Count() != 0) {
855 return NS_ERROR_SIGNED_JAR_ENTRY_MISSING;
856 }
857
858 // Return the reader to the caller if they want it
859 if (aZipReader) {
860 zip.forget(aZipReader);
861 }
862
863 // Return the signer's certificate to the reader if they want it.
864 // XXX: We should return an nsIX509CertList with the whole validated chain.
865 if (aSignerCert) {
866 CERTCertListNode* signerCertNode = CERT_LIST_HEAD(builtChain);
867 if (!signerCertNode || CERT_LIST_END(signerCertNode, builtChain) ||
868 !signerCertNode->cert) {
869 return NS_ERROR_FAILURE;
870 }
871 nsCOMPtr<nsIX509Cert> signerCert =
872 nsNSSCertificate::Create(signerCertNode->cert);
873 NS_ENSURE_TRUE(signerCert, NS_ERROR_OUT_OF_MEMORY);
874 signerCert.forget(aSignerCert);
875 }
876
877 return NS_OK;
878 }
879
880 nsresult
VerifySignedManifest(AppTrustedRoot aTrustedRoot,nsIInputStream * aManifestStream,nsIInputStream * aSignatureStream,nsIX509Cert ** aSignerCert)881 VerifySignedManifest(AppTrustedRoot aTrustedRoot,
882 nsIInputStream* aManifestStream,
883 nsIInputStream* aSignatureStream,
884 /*out, optional */ nsIX509Cert** aSignerCert)
885 {
886 NS_ENSURE_ARG(aManifestStream);
887 NS_ENSURE_ARG(aSignatureStream);
888
889 if (aSignerCert) {
890 *aSignerCert = nullptr;
891 }
892
893 // Load signature file in buffer
894 ScopedAutoSECItem signatureBuffer;
895 nsresult rv = ReadStream(aSignatureStream, signatureBuffer);
896 if (NS_FAILED(rv)) {
897 return rv;
898 }
899 signatureBuffer.type = siBuffer;
900
901 // Load manifest file in buffer
902 ScopedAutoSECItem manifestBuffer;
903 rv = ReadStream(aManifestStream, manifestBuffer);
904 if (NS_FAILED(rv)) {
905 return rv;
906 }
907
908 // Calculate SHA1 digest of the manifest buffer
909 Digest manifestCalculatedDigest;
910 rv = manifestCalculatedDigest.DigestBuf(SEC_OID_SHA1,
911 manifestBuffer.data,
912 manifestBuffer.len - 1); // buffer is null terminated
913 if (NS_WARN_IF(NS_FAILED(rv))) {
914 return rv;
915 }
916
917 // Get base64 encoded string from manifest buffer digest
918 UniquePORTString
919 base64EncDigest(NSSBase64_EncodeItem(nullptr, nullptr, 0,
920 const_cast<SECItem*>(&manifestCalculatedDigest.get())));
921 if (NS_WARN_IF(!base64EncDigest)) {
922 return NS_ERROR_OUT_OF_MEMORY;
923 }
924
925 // Calculate SHA1 digest of the base64 encoded string
926 Digest doubleDigest;
927 rv = doubleDigest.DigestBuf(SEC_OID_SHA1,
928 BitwiseCast<uint8_t*, char*>(base64EncDigest.get()),
929 strlen(base64EncDigest.get()));
930 if (NS_WARN_IF(NS_FAILED(rv))) {
931 return rv;
932 }
933
934 // Verify the manifest signature (signed digest of the base64 encoded string)
935 UniqueCERTCertList builtChain;
936 rv = VerifySignature(aTrustedRoot, signatureBuffer,
937 doubleDigest.get(), builtChain);
938 if (NS_FAILED(rv)) {
939 return rv;
940 }
941
942 // Return the signer's certificate to the reader if they want it.
943 if (aSignerCert) {
944 CERTCertListNode* signerCertNode = CERT_LIST_HEAD(builtChain);
945 if (!signerCertNode || CERT_LIST_END(signerCertNode, builtChain) ||
946 !signerCertNode->cert) {
947 return NS_ERROR_FAILURE;
948 }
949 nsCOMPtr<nsIX509Cert> signerCert =
950 nsNSSCertificate::Create(signerCertNode->cert);
951 if (NS_WARN_IF(!signerCert)) {
952 return NS_ERROR_OUT_OF_MEMORY;
953 }
954
955 signerCert.forget(aSignerCert);
956 }
957
958 return NS_OK;
959 }
960
961 class OpenSignedAppFileTask final : public CryptoTask
962 {
963 public:
OpenSignedAppFileTask(AppTrustedRoot aTrustedRoot,nsIFile * aJarFile,nsIOpenSignedAppFileCallback * aCallback)964 OpenSignedAppFileTask(AppTrustedRoot aTrustedRoot, nsIFile* aJarFile,
965 nsIOpenSignedAppFileCallback* aCallback)
966 : mTrustedRoot(aTrustedRoot)
967 , mJarFile(aJarFile)
968 , mCallback(new nsMainThreadPtrHolder<nsIOpenSignedAppFileCallback>(aCallback))
969 {
970 }
971
972 private:
CalculateResult()973 virtual nsresult CalculateResult() override
974 {
975 return OpenSignedAppFile(mTrustedRoot, mJarFile,
976 getter_AddRefs(mZipReader),
977 getter_AddRefs(mSignerCert));
978 }
979
980 // nsNSSCertificate implements nsNSSShutdownObject, so there's nothing that
981 // needs to be released
ReleaseNSSResources()982 virtual void ReleaseNSSResources() override { }
983
CallCallback(nsresult rv)984 virtual void CallCallback(nsresult rv) override
985 {
986 (void) mCallback->OpenSignedAppFileFinished(rv, mZipReader, mSignerCert);
987 }
988
989 const AppTrustedRoot mTrustedRoot;
990 const nsCOMPtr<nsIFile> mJarFile;
991 nsMainThreadPtrHandle<nsIOpenSignedAppFileCallback> mCallback;
992 nsCOMPtr<nsIZipReader> mZipReader; // out
993 nsCOMPtr<nsIX509Cert> mSignerCert; // out
994 };
995
996 class VerifySignedmanifestTask final : public CryptoTask
997 {
998 public:
VerifySignedmanifestTask(AppTrustedRoot aTrustedRoot,nsIInputStream * aManifestStream,nsIInputStream * aSignatureStream,nsIVerifySignedManifestCallback * aCallback)999 VerifySignedmanifestTask(AppTrustedRoot aTrustedRoot,
1000 nsIInputStream* aManifestStream,
1001 nsIInputStream* aSignatureStream,
1002 nsIVerifySignedManifestCallback* aCallback)
1003 : mTrustedRoot(aTrustedRoot)
1004 , mManifestStream(aManifestStream)
1005 , mSignatureStream(aSignatureStream)
1006 , mCallback(
1007 new nsMainThreadPtrHolder<nsIVerifySignedManifestCallback>(aCallback))
1008 {
1009 }
1010
1011 private:
CalculateResult()1012 virtual nsresult CalculateResult() override
1013 {
1014 return VerifySignedManifest(mTrustedRoot, mManifestStream,
1015 mSignatureStream, getter_AddRefs(mSignerCert));
1016 }
1017
1018 // nsNSSCertificate implements nsNSSShutdownObject, so there's nothing that
1019 // needs to be released
ReleaseNSSResources()1020 virtual void ReleaseNSSResources() override { }
1021
CallCallback(nsresult rv)1022 virtual void CallCallback(nsresult rv) override
1023 {
1024 (void) mCallback->VerifySignedManifestFinished(rv, mSignerCert);
1025 }
1026
1027 const AppTrustedRoot mTrustedRoot;
1028 const nsCOMPtr<nsIInputStream> mManifestStream;
1029 const nsCOMPtr<nsIInputStream> mSignatureStream;
1030 nsMainThreadPtrHandle<nsIVerifySignedManifestCallback> mCallback;
1031 nsCOMPtr<nsIX509Cert> mSignerCert; // out
1032 };
1033
1034 } // unnamed namespace
1035
1036 NS_IMETHODIMP
OpenSignedAppFileAsync(AppTrustedRoot aTrustedRoot,nsIFile * aJarFile,nsIOpenSignedAppFileCallback * aCallback)1037 nsNSSCertificateDB::OpenSignedAppFileAsync(
1038 AppTrustedRoot aTrustedRoot, nsIFile* aJarFile,
1039 nsIOpenSignedAppFileCallback* aCallback)
1040 {
1041 NS_ENSURE_ARG_POINTER(aJarFile);
1042 NS_ENSURE_ARG_POINTER(aCallback);
1043 RefPtr<OpenSignedAppFileTask> task(new OpenSignedAppFileTask(aTrustedRoot,
1044 aJarFile,
1045 aCallback));
1046 return task->Dispatch("SignedJAR");
1047 }
1048
1049 NS_IMETHODIMP
VerifySignedManifestAsync(AppTrustedRoot aTrustedRoot,nsIInputStream * aManifestStream,nsIInputStream * aSignatureStream,nsIVerifySignedManifestCallback * aCallback)1050 nsNSSCertificateDB::VerifySignedManifestAsync(
1051 AppTrustedRoot aTrustedRoot, nsIInputStream* aManifestStream,
1052 nsIInputStream* aSignatureStream, nsIVerifySignedManifestCallback* aCallback)
1053 {
1054 NS_ENSURE_ARG_POINTER(aManifestStream);
1055 NS_ENSURE_ARG_POINTER(aSignatureStream);
1056 NS_ENSURE_ARG_POINTER(aCallback);
1057
1058 RefPtr<VerifySignedmanifestTask> task(
1059 new VerifySignedmanifestTask(aTrustedRoot, aManifestStream,
1060 aSignatureStream, aCallback));
1061 return task->Dispatch("SignedManifest");
1062 }
1063
1064
1065 //
1066 // Signature verification for archives unpacked into a file structure
1067 //
1068
1069 // Finds the "*.rsa" signature file in the META-INF directory and returns
1070 // the name. It is an error if there are none or more than one .rsa file
1071 nsresult
FindSignatureFilename(nsIFile * aMetaDir,nsAString & aFilename)1072 FindSignatureFilename(nsIFile* aMetaDir,
1073 /*out*/ nsAString& aFilename)
1074 {
1075 nsCOMPtr<nsISimpleEnumerator> entries;
1076 nsresult rv = aMetaDir->GetDirectoryEntries(getter_AddRefs(entries));
1077 nsCOMPtr<nsIDirectoryEnumerator> files = do_QueryInterface(entries);
1078 if (NS_FAILED(rv) || !files) {
1079 return NS_ERROR_SIGNED_JAR_NOT_SIGNED;
1080 }
1081
1082 bool found = false;
1083 nsCOMPtr<nsIFile> file;
1084 rv = files->GetNextFile(getter_AddRefs(file));
1085
1086 while (NS_SUCCEEDED(rv) && file) {
1087 nsAutoString leafname;
1088 rv = file->GetLeafName(leafname);
1089 if (NS_SUCCEEDED(rv)) {
1090 if (StringEndsWith(leafname, NS_LITERAL_STRING(".rsa"))) {
1091 if (!found) {
1092 found = true;
1093 aFilename = leafname;
1094 } else {
1095 // second signature file is an error
1096 rv = NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
1097 break;
1098 }
1099 }
1100 rv = files->GetNextFile(getter_AddRefs(file));
1101 }
1102 }
1103
1104 if (!found) {
1105 rv = NS_ERROR_SIGNED_JAR_NOT_SIGNED;
1106 }
1107
1108 files->Close();
1109 return rv;
1110 }
1111
1112 // Loads the signature metadata file that matches the given filename in
1113 // the passed-in Meta-inf directory. If bufDigest is not null then on
1114 // success bufDigest will contain the SHA-1 digest of the entry.
1115 nsresult
LoadOneMetafile(nsIFile * aMetaDir,const nsAString & aFilename,SECItem & aBuf,Digest * aBufDigest)1116 LoadOneMetafile(nsIFile* aMetaDir,
1117 const nsAString& aFilename,
1118 /*out*/ SECItem& aBuf,
1119 /*optional, out*/ Digest* aBufDigest)
1120 {
1121 nsCOMPtr<nsIFile> metafile;
1122 nsresult rv = aMetaDir->Clone(getter_AddRefs(metafile));
1123 NS_ENSURE_SUCCESS(rv, rv);
1124
1125 rv = metafile->Append(aFilename);
1126 NS_ENSURE_SUCCESS(rv, rv);
1127
1128 bool exists;
1129 rv = metafile->Exists(&exists);
1130 if (NS_FAILED(rv) || !exists) {
1131 // we can call a missing .rsa file "unsigned" but FindSignatureFilename()
1132 // already found one: missing other metadata files means a broken signature.
1133 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
1134 }
1135
1136 nsCOMPtr<nsIInputStream> stream;
1137 rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), metafile);
1138 NS_ENSURE_SUCCESS(rv, rv);
1139
1140 rv = ReadStream(stream, aBuf);
1141 stream->Close();
1142 NS_ENSURE_SUCCESS(rv, rv);
1143
1144 if (aBufDigest) {
1145 rv = aBufDigest->DigestBuf(SEC_OID_SHA1, aBuf.data, aBuf.len - 1);
1146 NS_ENSURE_SUCCESS(rv, rv);
1147 }
1148
1149 return NS_OK;
1150 }
1151
1152 // Parses MANIFEST.MF and verifies the contents of the unpacked files
1153 // listed in the manifest.
1154 // The filenames of all entries will be returned in aMfItems. aBuf must
1155 // be a pre-allocated scratch buffer that is used for doing I/O.
1156 nsresult
ParseMFUnpacked(const char * aFilebuf,nsIFile * aDir,nsTHashtable<nsStringHashKey> & aMfItems,ScopedAutoSECItem & aBuf)1157 ParseMFUnpacked(const char* aFilebuf, nsIFile* aDir,
1158 /*out*/ nsTHashtable<nsStringHashKey>& aMfItems,
1159 ScopedAutoSECItem& aBuf)
1160 {
1161 nsresult rv;
1162
1163 const char* nextLineStart = aFilebuf;
1164
1165 rv = CheckManifestVersion(nextLineStart, NS_LITERAL_CSTRING(JAR_MF_HEADER));
1166 if (NS_FAILED(rv)) {
1167 return rv;
1168 }
1169
1170 // Skip the rest of the header section, which ends with a blank line.
1171 {
1172 nsAutoCString line;
1173 do {
1174 rv = ReadLine(nextLineStart, line);
1175 if (NS_FAILED(rv)) {
1176 return rv;
1177 }
1178 } while (line.Length() > 0);
1179
1180 // Manifest containing no file entries is OK, though useless.
1181 if (*nextLineStart == '\0') {
1182 return NS_OK;
1183 }
1184 }
1185
1186 nsAutoString curItemName;
1187 ScopedAutoSECItem digest;
1188
1189 for (;;) {
1190 nsAutoCString curLine;
1191 rv = ReadLine(nextLineStart, curLine);
1192 if (NS_FAILED(rv)) {
1193 return rv;
1194 }
1195
1196 if (curLine.Length() == 0) {
1197 // end of section (blank line or end-of-file)
1198
1199 if (curItemName.Length() == 0) {
1200 // '...Each section must start with an attribute with the name as
1201 // "Name",...', so every section must have a Name attribute.
1202 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
1203 }
1204
1205 if (digest.len == 0) {
1206 // We require every entry to have a digest, since we require every
1207 // entry to be signed and we don't allow duplicate entries.
1208 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
1209 }
1210
1211 if (aMfItems.Contains(curItemName)) {
1212 // Duplicate entry
1213 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
1214 }
1215
1216 // Verify that the file's content digest matches the digest from this
1217 // MF section.
1218 rv = VerifyFileContentDigest(aDir, curItemName, digest, aBuf);
1219 if (NS_FAILED(rv)) {
1220 return rv;
1221 }
1222
1223 aMfItems.PutEntry(curItemName);
1224
1225 if (*nextLineStart == '\0') {
1226 // end-of-file
1227 break;
1228 }
1229
1230 // reset so we know we haven't encountered either of these for the next
1231 // item yet.
1232 curItemName.Truncate();
1233 digest.reset();
1234
1235 continue; // skip the rest of the loop below
1236 }
1237
1238 nsAutoCString attrName;
1239 nsAutoCString attrValue;
1240 rv = ParseAttribute(curLine, attrName, attrValue);
1241 if (NS_FAILED(rv)) {
1242 return rv;
1243 }
1244
1245 // Lines to look for:
1246
1247 // (1) Digest:
1248 if (attrName.LowerCaseEqualsLiteral("sha1-digest")) {
1249 if (digest.len > 0) {
1250 // multiple SHA1 digests in section
1251 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
1252 }
1253
1254 rv = MapSECStatus(ATOB_ConvertAsciiToItem(&digest, attrValue.get()));
1255 if (NS_FAILED(rv)) {
1256 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
1257 }
1258
1259 continue;
1260 }
1261
1262 // (2) Name: associates this manifest section with a file in the jar.
1263 if (attrName.LowerCaseEqualsLiteral("name")) {
1264 if (MOZ_UNLIKELY(curItemName.Length() > 0)) {
1265 // multiple names in section
1266 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
1267 }
1268
1269 if (MOZ_UNLIKELY(attrValue.Length() == 0)) {
1270 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
1271 }
1272
1273 curItemName = NS_ConvertUTF8toUTF16(attrValue);
1274
1275 continue;
1276 }
1277
1278 // (3) Magic: the only other must-understand attribute
1279 if (attrName.LowerCaseEqualsLiteral("magic")) {
1280 // We don't understand any magic, so we can't verify an entry that
1281 // requires magic. Since we require every entry to have a valid
1282 // signature, we have no choice but to reject the entry.
1283 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
1284 }
1285
1286 // unrecognized attributes must be ignored
1287 }
1288
1289 return NS_OK;
1290 }
1291
1292 // recursively check a directory tree for files not in the list of
1293 // verified files we found in the manifest. For each file we find
1294 // Check it against the files found in the manifest. If the file wasn't
1295 // in the manifest then it's unsigned and we can stop looking. Otherwise
1296 // remove it from the collection so we can check leftovers later.
1297 //
1298 // @param aDir Directory to check
1299 // @param aPath Relative path to that directory (to check against aItems)
1300 // @param aItems All the files found
1301 // @param *Filename signature files that won't be in the manifest
1302 nsresult
CheckDirForUnsignedFiles(nsIFile * aDir,const nsString & aPath,nsTHashtable<nsStringHashKey> & aItems,const nsAString & sigFilename,const nsAString & sfFilename,const nsAString & mfFilename)1303 CheckDirForUnsignedFiles(nsIFile* aDir,
1304 const nsString& aPath,
1305 /* in/out */ nsTHashtable<nsStringHashKey>& aItems,
1306 const nsAString& sigFilename,
1307 const nsAString& sfFilename,
1308 const nsAString& mfFilename)
1309 {
1310 nsCOMPtr<nsISimpleEnumerator> entries;
1311 nsresult rv = aDir->GetDirectoryEntries(getter_AddRefs(entries));
1312 nsCOMPtr<nsIDirectoryEnumerator> files = do_QueryInterface(entries);
1313 if (NS_FAILED(rv) || !files) {
1314 return NS_ERROR_SIGNED_JAR_ENTRY_MISSING;
1315 }
1316
1317 bool inMeta = StringBeginsWith(aPath, NS_LITERAL_STRING(JAR_META_DIR));
1318
1319 while (NS_SUCCEEDED(rv)) {
1320 nsCOMPtr<nsIFile> file;
1321 rv = files->GetNextFile(getter_AddRefs(file));
1322 if (NS_FAILED(rv) || !file) {
1323 break;
1324 }
1325
1326 nsAutoString leafname;
1327 rv = file->GetLeafName(leafname);
1328 if (NS_FAILED(rv)) {
1329 return rv;
1330 }
1331
1332 nsAutoString curName(aPath + leafname);
1333
1334 bool isDir;
1335 rv = file->IsDirectory(&isDir);
1336 if (NS_FAILED(rv)) {
1337 return rv;
1338 }
1339
1340 // if it's a directory we need to recurse
1341 if (isDir) {
1342 curName.Append(NS_LITERAL_STRING("/"));
1343 rv = CheckDirForUnsignedFiles(file, curName, aItems,
1344 sigFilename, sfFilename, mfFilename);
1345 } else {
1346 // The files that comprise the signature mechanism are not covered by the
1347 // signature.
1348 //
1349 // XXX: This is OK for a single signature, but doesn't work for
1350 // multiple signatures because the metadata for the other signatures
1351 // is not signed either.
1352 if (inMeta && ( leafname == sigFilename ||
1353 leafname == sfFilename ||
1354 leafname == mfFilename )) {
1355 continue;
1356 }
1357
1358 // make sure the current file was found in the manifest
1359 nsStringHashKey* item = aItems.GetEntry(curName);
1360 if (!item) {
1361 return NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY;
1362 }
1363
1364 // Remove the item so we can check for leftover items later
1365 aItems.RemoveEntry(item);
1366 }
1367 }
1368 files->Close();
1369 return rv;
1370 }
1371
1372 /*
1373 * Verify the signature of a directory structure as if it were a
1374 * signed JAR file (used for unpacked JARs)
1375 */
1376 nsresult
VerifySignedDirectory(AppTrustedRoot aTrustedRoot,nsIFile * aDirectory,nsIX509Cert ** aSignerCert)1377 VerifySignedDirectory(AppTrustedRoot aTrustedRoot,
1378 nsIFile* aDirectory,
1379 /*out, optional */ nsIX509Cert** aSignerCert)
1380 {
1381 NS_ENSURE_ARG_POINTER(aDirectory);
1382
1383 if (aSignerCert) {
1384 *aSignerCert = nullptr;
1385 }
1386
1387 // Make sure there's a META-INF directory
1388
1389 nsCOMPtr<nsIFile> metaDir;
1390 nsresult rv = aDirectory->Clone(getter_AddRefs(metaDir));
1391 if (NS_FAILED(rv)) {
1392 return rv;
1393 }
1394 rv = metaDir->Append(NS_LITERAL_STRING(JAR_META_DIR));
1395 if (NS_FAILED(rv)) {
1396 return rv;
1397 }
1398
1399 bool exists;
1400 rv = metaDir->Exists(&exists);
1401 if (NS_FAILED(rv) || !exists) {
1402 return NS_ERROR_SIGNED_JAR_NOT_SIGNED;
1403 }
1404 bool isDirectory;
1405 rv = metaDir->IsDirectory(&isDirectory);
1406 if (NS_FAILED(rv) || !isDirectory) {
1407 return NS_ERROR_SIGNED_JAR_NOT_SIGNED;
1408 }
1409
1410 // Find and load the Signature (RSA) file
1411
1412 nsAutoString sigFilename;
1413 rv = FindSignatureFilename(metaDir, sigFilename);
1414 if (NS_FAILED(rv)) {
1415 return rv;
1416 }
1417
1418 ScopedAutoSECItem sigBuffer;
1419 rv = LoadOneMetafile(metaDir, sigFilename, sigBuffer, nullptr);
1420 if (NS_FAILED(rv)) {
1421 return NS_ERROR_SIGNED_JAR_NOT_SIGNED;
1422 }
1423
1424 // Load the signature (SF) file and verify the signature.
1425 // The .sf and .rsa files must have the same name apart from the extension.
1426
1427 nsAutoString sfFilename(Substring(sigFilename, 0, sigFilename.Length() - 3)
1428 + NS_LITERAL_STRING("sf"));
1429
1430 ScopedAutoSECItem sfBuffer;
1431 Digest sfCalculatedDigest;
1432 rv = LoadOneMetafile(metaDir, sfFilename, sfBuffer, &sfCalculatedDigest);
1433 if (NS_FAILED(rv)) {
1434 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
1435 }
1436
1437 sigBuffer.type = siBuffer;
1438 UniqueCERTCertList builtChain;
1439 rv = VerifySignature(aTrustedRoot, sigBuffer, sfCalculatedDigest.get(),
1440 builtChain);
1441 if (NS_FAILED(rv)) {
1442 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
1443 }
1444
1445 // Get the expected manifest hash from the signed .sf file
1446
1447 ScopedAutoSECItem mfDigest;
1448 rv = ParseSF(BitwiseCast<char*, unsigned char*>(sfBuffer.data), mfDigest);
1449 if (NS_FAILED(rv)) {
1450 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
1451 }
1452
1453 // Load manifest (MF) file and verify signature
1454
1455 nsAutoString mfFilename(NS_LITERAL_STRING("manifest.mf"));
1456 ScopedAutoSECItem manifestBuffer;
1457 Digest mfCalculatedDigest;
1458 rv = LoadOneMetafile(metaDir, mfFilename, manifestBuffer, &mfCalculatedDigest);
1459 if (NS_FAILED(rv)) {
1460 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
1461 }
1462
1463 if (SECITEM_CompareItem(&mfDigest, &mfCalculatedDigest.get()) != SECEqual) {
1464 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
1465 }
1466
1467 // Parse manifest and verify signed hash of all listed files
1468
1469 // Allocate the I/O buffer only once per JAR, instead of once per entry, in
1470 // order to minimize malloc/free calls and in order to avoid fragmenting
1471 // memory.
1472 ScopedAutoSECItem buf(128 * 1024);
1473
1474 nsTHashtable<nsStringHashKey> items;
1475 rv = ParseMFUnpacked(BitwiseCast<char*, unsigned char*>(manifestBuffer.data),
1476 aDirectory, items, buf);
1477 if (NS_FAILED(rv)){
1478 return rv;
1479 }
1480
1481 // We've checked that everything listed in the manifest exists and is signed
1482 // correctly. Now check on disk for extra (unsigned) files.
1483 // Deletes found entries from items as it goes.
1484 rv = CheckDirForUnsignedFiles(aDirectory, EmptyString(), items,
1485 sigFilename, sfFilename, mfFilename);
1486 if (NS_FAILED(rv)) {
1487 return rv;
1488 }
1489
1490 // We verified that every entry that we require to be signed is signed. But,
1491 // were there any missing entries--that is, entries that are mentioned in the
1492 // manifest but missing from the directory tree? (There shouldn't be given
1493 // ParseMFUnpacked() checking them all, but it's a cheap sanity check.)
1494 if (items.Count() != 0) {
1495 return NS_ERROR_SIGNED_JAR_ENTRY_MISSING;
1496 }
1497
1498 // Return the signer's certificate to the reader if they want it.
1499 // XXX: We should return an nsIX509CertList with the whole validated chain.
1500 if (aSignerCert) {
1501 CERTCertListNode* signerCertNode = CERT_LIST_HEAD(builtChain);
1502 if (!signerCertNode || CERT_LIST_END(signerCertNode, builtChain) ||
1503 !signerCertNode->cert) {
1504 return NS_ERROR_FAILURE;
1505 }
1506 nsCOMPtr<nsIX509Cert> signerCert =
1507 nsNSSCertificate::Create(signerCertNode->cert);
1508 NS_ENSURE_TRUE(signerCert, NS_ERROR_OUT_OF_MEMORY);
1509 signerCert.forget(aSignerCert);
1510 }
1511
1512 return NS_OK;
1513 }
1514
1515 class VerifySignedDirectoryTask final : public CryptoTask
1516 {
1517 public:
VerifySignedDirectoryTask(AppTrustedRoot aTrustedRoot,nsIFile * aUnpackedJar,nsIVerifySignedDirectoryCallback * aCallback)1518 VerifySignedDirectoryTask(AppTrustedRoot aTrustedRoot, nsIFile* aUnpackedJar,
1519 nsIVerifySignedDirectoryCallback* aCallback)
1520 : mTrustedRoot(aTrustedRoot)
1521 , mDirectory(aUnpackedJar)
1522 , mCallback(new nsMainThreadPtrHolder<nsIVerifySignedDirectoryCallback>(aCallback))
1523 {
1524 }
1525
1526 private:
CalculateResult()1527 virtual nsresult CalculateResult() override
1528 {
1529 return VerifySignedDirectory(mTrustedRoot,
1530 mDirectory,
1531 getter_AddRefs(mSignerCert));
1532 }
1533
1534 // This class doesn't directly hold NSS resources so there's nothing that
1535 // needs to be released
ReleaseNSSResources()1536 virtual void ReleaseNSSResources() override { }
1537
CallCallback(nsresult rv)1538 virtual void CallCallback(nsresult rv) override
1539 {
1540 (void) mCallback->VerifySignedDirectoryFinished(rv, mSignerCert);
1541 }
1542
1543 const AppTrustedRoot mTrustedRoot;
1544 const nsCOMPtr<nsIFile> mDirectory;
1545 nsMainThreadPtrHandle<nsIVerifySignedDirectoryCallback> mCallback;
1546 nsCOMPtr<nsIX509Cert> mSignerCert; // out
1547 };
1548
1549 NS_IMETHODIMP
VerifySignedDirectoryAsync(AppTrustedRoot aTrustedRoot,nsIFile * aUnpackedJar,nsIVerifySignedDirectoryCallback * aCallback)1550 nsNSSCertificateDB::VerifySignedDirectoryAsync(
1551 AppTrustedRoot aTrustedRoot, nsIFile* aUnpackedJar,
1552 nsIVerifySignedDirectoryCallback* aCallback)
1553 {
1554 NS_ENSURE_ARG_POINTER(aUnpackedJar);
1555 NS_ENSURE_ARG_POINTER(aCallback);
1556 RefPtr<VerifySignedDirectoryTask> task(new VerifySignedDirectoryTask(aTrustedRoot,
1557 aUnpackedJar,
1558 aCallback));
1559 return task->Dispatch("UnpackedJar");
1560 }
1561