security/apps/AppSignatureVerification.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial