1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/security/apps/AppSignatureVerification.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,845 @@ 1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim: set ts=2 et sw=2 tw=80: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#ifdef MOZ_LOGGING 1.11 +#define FORCE_PR_LOG 1 1.12 +#endif 1.13 + 1.14 +#include "nsNSSCertificateDB.h" 1.15 + 1.16 +#include "pkix/pkix.h" 1.17 +#include "mozilla/RefPtr.h" 1.18 +#include "CryptoTask.h" 1.19 +#include "AppTrustDomain.h" 1.20 +#include "nsComponentManagerUtils.h" 1.21 +#include "nsCOMPtr.h" 1.22 +#include "nsHashKeys.h" 1.23 +#include "nsIFile.h" 1.24 +#include "nsIInputStream.h" 1.25 +#include "nsIStringEnumerator.h" 1.26 +#include "nsIZipReader.h" 1.27 +#include "nsNSSCertificate.h" 1.28 +#include "nsProxyRelease.h" 1.29 +#include "nsString.h" 1.30 +#include "nsTHashtable.h" 1.31 +#include "ScopedNSSTypes.h" 1.32 + 1.33 +#include "base64.h" 1.34 +#include "certdb.h" 1.35 +#include "secmime.h" 1.36 +#include "plstr.h" 1.37 +#include "prlog.h" 1.38 + 1.39 +using namespace mozilla::pkix; 1.40 +using namespace mozilla; 1.41 +using namespace mozilla::psm; 1.42 + 1.43 +#ifdef PR_LOGGING 1.44 +extern PRLogModuleInfo* gPIPNSSLog; 1.45 +#endif 1.46 + 1.47 +namespace { 1.48 + 1.49 +// Finds exactly one (signature metadata) entry that matches the given 1.50 +// search pattern, and then load it. Fails if there are no matches or if 1.51 +// there is more than one match. If bugDigest is not null then on success 1.52 +// bufDigest will contain the SHA-1 digeset of the entry. 1.53 +nsresult 1.54 +FindAndLoadOneEntry(nsIZipReader * zip, 1.55 + const nsACString & searchPattern, 1.56 + /*out*/ nsACString & filename, 1.57 + /*out*/ SECItem & buf, 1.58 + /*optional, out*/ Digest * bufDigest) 1.59 +{ 1.60 + nsCOMPtr<nsIUTF8StringEnumerator> files; 1.61 + nsresult rv = zip->FindEntries(searchPattern, getter_AddRefs(files)); 1.62 + if (NS_FAILED(rv) || !files) { 1.63 + return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; 1.64 + } 1.65 + 1.66 + bool more; 1.67 + rv = files->HasMore(&more); 1.68 + NS_ENSURE_SUCCESS(rv, rv); 1.69 + if (!more) { 1.70 + return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; 1.71 + } 1.72 + 1.73 + rv = files->GetNext(filename); 1.74 + NS_ENSURE_SUCCESS(rv, rv); 1.75 + 1.76 + // Check if there is more than one match, if so then error! 1.77 + rv = files->HasMore(&more); 1.78 + NS_ENSURE_SUCCESS(rv, rv); 1.79 + if (more) { 1.80 + return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; 1.81 + } 1.82 + 1.83 + nsCOMPtr<nsIInputStream> stream; 1.84 + rv = zip->GetInputStream(filename, getter_AddRefs(stream)); 1.85 + NS_ENSURE_SUCCESS(rv, rv); 1.86 + 1.87 + // The size returned by Available() might be inaccurate so we need to check 1.88 + // that Available() matches up with the actual length of the file. 1.89 + uint64_t len64; 1.90 + rv = stream->Available(&len64); 1.91 + NS_ENSURE_SUCCESS(rv, rv); 1.92 + 1.93 + 1.94 + // Cap the maximum accepted size of signature-related files at 1MB (which is 1.95 + // still crazily huge) to avoid OOM. The uncompressed length of an entry can be 1.96 + // hundreds of times larger than the compressed version, especially if 1.97 + // someone has speifically crafted the entry to cause OOM or to consume 1.98 + // massive amounts of disk space. 1.99 + // 1.100 + // Also, keep in mind bug 164695 and that we must leave room for 1.101 + // null-terminating the buffer. 1.102 + static const uint32_t MAX_LENGTH = 1024 * 1024; 1.103 + static_assert(MAX_LENGTH < UINT32_MAX, "MAX_LENGTH < UINT32_MAX"); 1.104 + NS_ENSURE_TRUE(len64 < MAX_LENGTH, NS_ERROR_FILE_CORRUPTED); 1.105 + NS_ENSURE_TRUE(len64 < UINT32_MAX, NS_ERROR_FILE_CORRUPTED); // bug 164695 1.106 + SECITEM_AllocItem(buf, static_cast<uint32_t>(len64 + 1)); 1.107 + 1.108 + // buf.len == len64 + 1. We attempt to read len64 + 1 bytes instead of len64, 1.109 + // so that we can check whether the metadata in the ZIP for the entry is 1.110 + // incorrect. 1.111 + uint32_t bytesRead; 1.112 + rv = stream->Read(char_ptr_cast(buf.data), buf.len, &bytesRead); 1.113 + NS_ENSURE_SUCCESS(rv, rv); 1.114 + if (bytesRead != len64) { 1.115 + return NS_ERROR_SIGNED_JAR_ENTRY_INVALID; 1.116 + } 1.117 + 1.118 + buf.data[buf.len - 1] = 0; // null-terminate 1.119 + 1.120 + if (bufDigest) { 1.121 + rv = bufDigest->DigestBuf(SEC_OID_SHA1, buf.data, buf.len - 1); 1.122 + NS_ENSURE_SUCCESS(rv, rv); 1.123 + } 1.124 + 1.125 + return NS_OK; 1.126 +} 1.127 + 1.128 +// Verify the digest of an entry. We avoid loading the entire entry into memory 1.129 +// at once, which would require memory in proportion to the size of the largest 1.130 +// entry. Instead, we require only a small, fixed amount of memory. 1.131 +// 1.132 +// @param digestFromManifest The digest that we're supposed to check the file's 1.133 +// contents against, from the manifest 1.134 +// @param buf A scratch buffer that we use for doing the I/O, which must have 1.135 +// already been allocated. The size of this buffer is the unit 1.136 +// size of our I/O. 1.137 +nsresult 1.138 +VerifyEntryContentDigest(nsIZipReader * zip, const nsACString & aFilename, 1.139 + const SECItem & digestFromManifest, SECItem & buf) 1.140 +{ 1.141 + MOZ_ASSERT(buf.len > 0); 1.142 + if (digestFromManifest.len != SHA1_LENGTH) 1.143 + return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; 1.144 + 1.145 + nsresult rv; 1.146 + 1.147 + nsCOMPtr<nsIInputStream> stream; 1.148 + rv = zip->GetInputStream(aFilename, getter_AddRefs(stream)); 1.149 + if (NS_FAILED(rv)) { 1.150 + return NS_ERROR_SIGNED_JAR_ENTRY_MISSING; 1.151 + } 1.152 + 1.153 + uint64_t len64; 1.154 + rv = stream->Available(&len64); 1.155 + NS_ENSURE_SUCCESS(rv, rv); 1.156 + if (len64 > UINT32_MAX) { 1.157 + return NS_ERROR_SIGNED_JAR_ENTRY_TOO_LARGE; 1.158 + } 1.159 + 1.160 + ScopedPK11Context digestContext(PK11_CreateDigestContext(SEC_OID_SHA1)); 1.161 + if (!digestContext) { 1.162 + return PRErrorCode_to_nsresult(PR_GetError()); 1.163 + } 1.164 + 1.165 + rv = MapSECStatus(PK11_DigestBegin(digestContext)); 1.166 + NS_ENSURE_SUCCESS(rv, rv); 1.167 + 1.168 + uint64_t totalBytesRead = 0; 1.169 + for (;;) { 1.170 + uint32_t bytesRead; 1.171 + rv = stream->Read(char_ptr_cast(buf.data), buf.len, &bytesRead); 1.172 + NS_ENSURE_SUCCESS(rv, rv); 1.173 + 1.174 + if (bytesRead == 0) { 1.175 + break; // EOF 1.176 + } 1.177 + 1.178 + totalBytesRead += bytesRead; 1.179 + if (totalBytesRead >= UINT32_MAX) { 1.180 + return NS_ERROR_SIGNED_JAR_ENTRY_TOO_LARGE; 1.181 + } 1.182 + 1.183 + rv = MapSECStatus(PK11_DigestOp(digestContext, buf.data, bytesRead)); 1.184 + NS_ENSURE_SUCCESS(rv, rv); 1.185 + } 1.186 + 1.187 + if (totalBytesRead != len64) { 1.188 + // The metadata we used for Available() doesn't match the actual size of 1.189 + // the entry. 1.190 + return NS_ERROR_SIGNED_JAR_ENTRY_INVALID; 1.191 + } 1.192 + 1.193 + // Verify that the digests match. 1.194 + Digest digest; 1.195 + rv = digest.End(SEC_OID_SHA1, digestContext); 1.196 + NS_ENSURE_SUCCESS(rv, rv); 1.197 + 1.198 + if (SECITEM_CompareItem(&digestFromManifest, &digest.get()) != SECEqual) { 1.199 + return NS_ERROR_SIGNED_JAR_MODIFIED_ENTRY; 1.200 + } 1.201 + 1.202 + return NS_OK; 1.203 +} 1.204 + 1.205 +// On input, nextLineStart is the start of the current line. On output, 1.206 +// nextLineStart is the start of the next line. 1.207 +nsresult 1.208 +ReadLine(/*in/out*/ const char* & nextLineStart, /*out*/ nsCString & line, 1.209 + bool allowContinuations = true) 1.210 +{ 1.211 + line.Truncate(); 1.212 + size_t previousLength = 0; 1.213 + size_t currentLength = 0; 1.214 + for (;;) { 1.215 + const char* eol = PL_strpbrk(nextLineStart, "\r\n"); 1.216 + 1.217 + if (!eol) { // Reached end of file before newline 1.218 + eol = nextLineStart + strlen(nextLineStart); 1.219 + } 1.220 + 1.221 + previousLength = currentLength; 1.222 + line.Append(nextLineStart, eol - nextLineStart); 1.223 + currentLength = line.Length(); 1.224 + 1.225 + // The spec says "No line may be longer than 72 bytes (not characters)" 1.226 + // in its UTF8-encoded form. 1.227 + static const size_t lineLimit = 72; 1.228 + if (currentLength - previousLength > lineLimit) { 1.229 + return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; 1.230 + } 1.231 + 1.232 + // The spec says: "Implementations should support 65535-byte 1.233 + // (not character) header values..." 1.234 + if (currentLength > 65535) { 1.235 + return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; 1.236 + } 1.237 + 1.238 + if (*eol == '\r') { 1.239 + ++eol; 1.240 + } 1.241 + if (*eol == '\n') { 1.242 + ++eol; 1.243 + } 1.244 + 1.245 + nextLineStart = eol; 1.246 + 1.247 + if (*eol != ' ') { 1.248 + // not a continuation 1.249 + return NS_OK; 1.250 + } 1.251 + 1.252 + // continuation 1.253 + if (!allowContinuations) { 1.254 + return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; 1.255 + } 1.256 + 1.257 + ++nextLineStart; // skip space and keep appending 1.258 + } 1.259 +} 1.260 + 1.261 +// The header strings are defined in the JAR specification. 1.262 +#define JAR_MF_SEARCH_STRING "(M|/M)ETA-INF/(M|m)(ANIFEST|anifest).(MF|mf)$" 1.263 +#define JAR_SF_SEARCH_STRING "(M|/M)ETA-INF/*.(SF|sf)$" 1.264 +#define JAR_RSA_SEARCH_STRING "(M|/M)ETA-INF/*.(RSA|rsa)$" 1.265 +#define JAR_MF_HEADER "Manifest-Version: 1.0" 1.266 +#define JAR_SF_HEADER "Signature-Version: 1.0" 1.267 + 1.268 +nsresult 1.269 +ParseAttribute(const nsAutoCString & curLine, 1.270 + /*out*/ nsAutoCString & attrName, 1.271 + /*out*/ nsAutoCString & attrValue) 1.272 +{ 1.273 + // Find the colon that separates the name from the value. 1.274 + int32_t colonPos = curLine.FindChar(':'); 1.275 + if (colonPos == kNotFound) { 1.276 + return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; 1.277 + } 1.278 + 1.279 + // set attrName to the name, skipping spaces between the name and colon 1.280 + int32_t nameEnd = colonPos; 1.281 + for (;;) { 1.282 + if (nameEnd == 0) { 1.283 + return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; // colon with no name 1.284 + } 1.285 + if (curLine[nameEnd - 1] != ' ') 1.286 + break; 1.287 + --nameEnd; 1.288 + } 1.289 + curLine.Left(attrName, nameEnd); 1.290 + 1.291 + // Set attrValue to the value, skipping spaces between the colon and the 1.292 + // value. The value may be empty. 1.293 + int32_t valueStart = colonPos + 1; 1.294 + int32_t curLineLength = curLine.Length(); 1.295 + while (valueStart != curLineLength && curLine[valueStart] == ' ') { 1.296 + ++valueStart; 1.297 + } 1.298 + curLine.Right(attrValue, curLineLength - valueStart); 1.299 + 1.300 + return NS_OK; 1.301 +} 1.302 + 1.303 +// Parses the version line of the MF or SF header. 1.304 +nsresult 1.305 +CheckManifestVersion(const char* & nextLineStart, 1.306 + const nsACString & expectedHeader) 1.307 +{ 1.308 + // The JAR spec says: "Manifest-Version and Signature-Version must be first, 1.309 + // and in exactly that case (so that they can be recognized easily as magic 1.310 + // strings)." 1.311 + nsAutoCString curLine; 1.312 + nsresult rv = ReadLine(nextLineStart, curLine, false); 1.313 + if (NS_FAILED(rv)) { 1.314 + return rv; 1.315 + } 1.316 + if (!curLine.Equals(expectedHeader)) { 1.317 + return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; 1.318 + } 1.319 + return NS_OK; 1.320 +} 1.321 + 1.322 +// Parses a signature file (SF) as defined in the JDK 8 JAR Specification. 1.323 +// 1.324 +// The SF file *must* contain exactly one SHA1-Digest-Manifest attribute in 1.325 +// the main section. All other sections are ignored. This means that this will 1.326 +// NOT parse old-style signature files that have separate digests per entry. 1.327 +// The JDK8 x-Digest-Manifest variant is better because: 1.328 +// 1.329 +// (1) It allows us to follow the principle that we should minimize the 1.330 +// processing of data that we do before we verify its signature. In 1.331 +// particular, with the x-Digest-Manifest style, we can verify the digest 1.332 +// of MANIFEST.MF before we parse it, which prevents malicious JARs 1.333 +// exploiting our MANIFEST.MF parser. 1.334 +// (2) It is more time-efficient and space-efficient to have one 1.335 +// x-Digest-Manifest instead of multiple x-Digest values. 1.336 +// 1.337 +// In order to get benefit (1), we do NOT implement the fallback to the older 1.338 +// mechanism as the spec requires/suggests. Also, for simplity's sake, we only 1.339 +// support exactly one SHA1-Digest-Manifest attribute, and no other 1.340 +// algorithms. 1.341 +// 1.342 +// filebuf must be null-terminated. On output, mfDigest will contain the 1.343 +// decoded value of SHA1-Digest-Manifest. 1.344 +nsresult 1.345 +ParseSF(const char* filebuf, /*out*/ SECItem & mfDigest) 1.346 +{ 1.347 + nsresult rv; 1.348 + 1.349 + const char* nextLineStart = filebuf; 1.350 + rv = CheckManifestVersion(nextLineStart, NS_LITERAL_CSTRING(JAR_SF_HEADER)); 1.351 + if (NS_FAILED(rv)) 1.352 + return rv; 1.353 + 1.354 + // Find SHA1-Digest-Manifest 1.355 + for (;;) { 1.356 + nsAutoCString curLine; 1.357 + rv = ReadLine(nextLineStart, curLine); 1.358 + if (NS_FAILED(rv)) { 1.359 + return rv; 1.360 + } 1.361 + 1.362 + if (curLine.Length() == 0) { 1.363 + // End of main section (blank line or end-of-file), and no 1.364 + // SHA1-Digest-Manifest found. 1.365 + return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; 1.366 + } 1.367 + 1.368 + nsAutoCString attrName; 1.369 + nsAutoCString attrValue; 1.370 + rv = ParseAttribute(curLine, attrName, attrValue); 1.371 + if (NS_FAILED(rv)) { 1.372 + return rv; 1.373 + } 1.374 + 1.375 + if (attrName.LowerCaseEqualsLiteral("sha1-digest-manifest")) { 1.376 + rv = MapSECStatus(ATOB_ConvertAsciiToItem(&mfDigest, attrValue.get())); 1.377 + if (NS_FAILED(rv)) { 1.378 + return rv; 1.379 + } 1.380 + 1.381 + // There could be multiple SHA1-Digest-Manifest attributes, which 1.382 + // would be an error, but it's better to just skip any erroneous 1.383 + // duplicate entries rather than trying to detect them, because: 1.384 + // 1.385 + // (1) It's simpler, and simpler generally means more secure 1.386 + // (2) An attacker can't make us accept a JAR we would otherwise 1.387 + // reject just by adding additional SHA1-Digest-Manifest 1.388 + // attributes. 1.389 + break; 1.390 + } 1.391 + 1.392 + // ignore unrecognized attributes 1.393 + } 1.394 + 1.395 + return NS_OK; 1.396 +} 1.397 + 1.398 +// Parses MANIFEST.MF. The filenames of all entries will be returned in 1.399 +// mfItems. buf must be a pre-allocated scratch buffer that is used for doing 1.400 +// I/O. 1.401 +nsresult 1.402 +ParseMF(const char* filebuf, nsIZipReader * zip, 1.403 + /*out*/ nsTHashtable<nsCStringHashKey> & mfItems, 1.404 + ScopedAutoSECItem & buf) 1.405 +{ 1.406 + nsresult rv; 1.407 + 1.408 + const char* nextLineStart = filebuf; 1.409 + 1.410 + rv = CheckManifestVersion(nextLineStart, NS_LITERAL_CSTRING(JAR_MF_HEADER)); 1.411 + if (NS_FAILED(rv)) { 1.412 + return rv; 1.413 + } 1.414 + 1.415 + // Skip the rest of the header section, which ends with a blank line. 1.416 + { 1.417 + nsAutoCString line; 1.418 + do { 1.419 + rv = ReadLine(nextLineStart, line); 1.420 + if (NS_FAILED(rv)) { 1.421 + return rv; 1.422 + } 1.423 + } while (line.Length() > 0); 1.424 + 1.425 + // Manifest containing no file entries is OK, though useless. 1.426 + if (*nextLineStart == '\0') { 1.427 + return NS_OK; 1.428 + } 1.429 + } 1.430 + 1.431 + nsAutoCString curItemName; 1.432 + ScopedAutoSECItem digest; 1.433 + 1.434 + for (;;) { 1.435 + nsAutoCString curLine; 1.436 + rv = ReadLine(nextLineStart, curLine); 1.437 + NS_ENSURE_SUCCESS(rv, rv); 1.438 + 1.439 + if (curLine.Length() == 0) { 1.440 + // end of section (blank line or end-of-file) 1.441 + 1.442 + if (curItemName.Length() == 0) { 1.443 + // '...Each section must start with an attribute with the name as 1.444 + // "Name",...', so every section must have a Name attribute. 1.445 + return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; 1.446 + } 1.447 + 1.448 + if (digest.len == 0) { 1.449 + // We require every entry to have a digest, since we require every 1.450 + // entry to be signed and we don't allow duplicate entries. 1.451 + return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; 1.452 + } 1.453 + 1.454 + if (mfItems.Contains(curItemName)) { 1.455 + // Duplicate entry 1.456 + return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; 1.457 + } 1.458 + 1.459 + // Verify that the entry's content digest matches the digest from this 1.460 + // MF section. 1.461 + rv = VerifyEntryContentDigest(zip, curItemName, digest, buf); 1.462 + if (NS_FAILED(rv)) 1.463 + return rv; 1.464 + 1.465 + mfItems.PutEntry(curItemName); 1.466 + 1.467 + if (*nextLineStart == '\0') // end-of-file 1.468 + break; 1.469 + 1.470 + // reset so we know we haven't encountered either of these for the next 1.471 + // item yet. 1.472 + curItemName.Truncate(); 1.473 + digest.reset(); 1.474 + 1.475 + continue; // skip the rest of the loop below 1.476 + } 1.477 + 1.478 + nsAutoCString attrName; 1.479 + nsAutoCString attrValue; 1.480 + rv = ParseAttribute(curLine, attrName, attrValue); 1.481 + if (NS_FAILED(rv)) { 1.482 + return rv; 1.483 + } 1.484 + 1.485 + // Lines to look for: 1.486 + 1.487 + // (1) Digest: 1.488 + if (attrName.LowerCaseEqualsLiteral("sha1-digest")) 1.489 + { 1.490 + if (digest.len > 0) // multiple SHA1 digests in section 1.491 + return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; 1.492 + 1.493 + rv = MapSECStatus(ATOB_ConvertAsciiToItem(&digest, attrValue.get())); 1.494 + if (NS_FAILED(rv)) 1.495 + return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; 1.496 + 1.497 + continue; 1.498 + } 1.499 + 1.500 + // (2) Name: associates this manifest section with a file in the jar. 1.501 + if (attrName.LowerCaseEqualsLiteral("name")) 1.502 + { 1.503 + if (MOZ_UNLIKELY(curItemName.Length() > 0)) // multiple names in section 1.504 + return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; 1.505 + 1.506 + if (MOZ_UNLIKELY(attrValue.Length() == 0)) 1.507 + return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; 1.508 + 1.509 + curItemName = attrValue; 1.510 + 1.511 + continue; 1.512 + } 1.513 + 1.514 + // (3) Magic: the only other must-understand attribute 1.515 + if (attrName.LowerCaseEqualsLiteral("magic")) { 1.516 + // We don't understand any magic, so we can't verify an entry that 1.517 + // requires magic. Since we require every entry to have a valid 1.518 + // signature, we have no choice but to reject the entry. 1.519 + return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; 1.520 + } 1.521 + 1.522 + // unrecognized attributes must be ignored 1.523 + } 1.524 + 1.525 + return NS_OK; 1.526 +} 1.527 + 1.528 +nsresult 1.529 +VerifySignature(AppTrustedRoot trustedRoot, 1.530 + const SECItem& buffer, const SECItem& detachedDigest, 1.531 + /*out*/ mozilla::pkix::ScopedCERTCertList& builtChain) 1.532 +{ 1.533 + mozilla::pkix::ScopedPtr<NSSCMSMessage, NSS_CMSMessage_Destroy> 1.534 + cmsMsg(NSS_CMSMessage_CreateFromDER(const_cast<SECItem*>(&buffer), nullptr, 1.535 + nullptr, nullptr, nullptr, nullptr, 1.536 + nullptr)); 1.537 + if (!cmsMsg) { 1.538 + return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING; 1.539 + } 1.540 + 1.541 + if (!NSS_CMSMessage_IsSigned(cmsMsg.get())) { 1.542 + PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("CMS message isn't signed")); 1.543 + return NS_ERROR_CMS_VERIFY_NOT_SIGNED; 1.544 + } 1.545 + 1.546 + NSSCMSContentInfo* cinfo = NSS_CMSMessage_ContentLevel(cmsMsg.get(), 0); 1.547 + if (!cinfo) { 1.548 + return NS_ERROR_CMS_VERIFY_NO_CONTENT_INFO; 1.549 + } 1.550 + 1.551 + // signedData is non-owning 1.552 + NSSCMSSignedData* signedData = 1.553 + reinterpret_cast<NSSCMSSignedData*>(NSS_CMSContentInfo_GetContent(cinfo)); 1.554 + if (!signedData) { 1.555 + return NS_ERROR_CMS_VERIFY_NO_CONTENT_INFO; 1.556 + } 1.557 + 1.558 + // Set digest value. 1.559 + if (NSS_CMSSignedData_SetDigestValue(signedData, SEC_OID_SHA1, 1.560 + const_cast<SECItem*>(&detachedDigest))) { 1.561 + return NS_ERROR_CMS_VERIFY_BAD_DIGEST; 1.562 + } 1.563 + 1.564 + // Parse the certificates into CERTCertificate objects held in memory, so that 1.565 + // AppTrustDomain will be able to find them during path building. 1.566 + mozilla::pkix::ScopedCERTCertList certs(CERT_NewCertList()); 1.567 + if (!certs) { 1.568 + return NS_ERROR_OUT_OF_MEMORY; 1.569 + } 1.570 + if (signedData->rawCerts) { 1.571 + for (size_t i = 0; signedData->rawCerts[i]; ++i) { 1.572 + mozilla::pkix::ScopedCERTCertificate 1.573 + cert(CERT_NewTempCertificate(CERT_GetDefaultCertDB(), 1.574 + signedData->rawCerts[i], nullptr, false, 1.575 + true)); 1.576 + // Skip certificates that fail to parse 1.577 + if (cert) { 1.578 + if (CERT_AddCertToListTail(certs.get(), cert.get()) == SECSuccess) { 1.579 + cert.release(); // ownership transfered 1.580 + } else { 1.581 + return NS_ERROR_OUT_OF_MEMORY; 1.582 + } 1.583 + } 1.584 + } 1.585 + } 1.586 + 1.587 + // Get the end-entity certificate. 1.588 + int numSigners = NSS_CMSSignedData_SignerInfoCount(signedData); 1.589 + if (NS_WARN_IF(numSigners != 1)) { 1.590 + return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING; 1.591 + } 1.592 + // signer is non-owning. 1.593 + NSSCMSSignerInfo* signer = NSS_CMSSignedData_GetSignerInfo(signedData, 0); 1.594 + if (NS_WARN_IF(!signer)) { 1.595 + return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING; 1.596 + } 1.597 + // cert is signerCert 1.598 + CERTCertificate* signerCert = 1.599 + NSS_CMSSignerInfo_GetSigningCertificate(signer, CERT_GetDefaultCertDB()); 1.600 + if (!signerCert) { 1.601 + return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING; 1.602 + } 1.603 + 1.604 + // Verify certificate. 1.605 + AppTrustDomain trustDomain(nullptr); // TODO: null pinArg 1.606 + if (trustDomain.SetTrustedRoot(trustedRoot) != SECSuccess) { 1.607 + return MapSECStatus(SECFailure); 1.608 + } 1.609 + if (BuildCertChain(trustDomain, signerCert, PR_Now(), MustBeEndEntity, 1.610 + KeyUsage::digitalSignature, 1.611 + SEC_OID_EXT_KEY_USAGE_CODE_SIGN, 1.612 + SEC_OID_X509_ANY_POLICY, nullptr, builtChain) 1.613 + != SECSuccess) { 1.614 + return MapSECStatus(SECFailure); 1.615 + } 1.616 + 1.617 + // See NSS_CMSContentInfo_GetContentTypeOID, which isn't exported from NSS. 1.618 + SECOidData* contentTypeOidData = 1.619 + SECOID_FindOID(&signedData->contentInfo.contentType); 1.620 + if (!contentTypeOidData) { 1.621 + return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING; 1.622 + } 1.623 + 1.624 + return MapSECStatus(NSS_CMSSignerInfo_Verify(signer, 1.625 + const_cast<SECItem*>(&detachedDigest), 1.626 + &contentTypeOidData->oid)); 1.627 +} 1.628 + 1.629 +NS_IMETHODIMP 1.630 +OpenSignedAppFile(AppTrustedRoot aTrustedRoot, nsIFile* aJarFile, 1.631 + /*out, optional */ nsIZipReader** aZipReader, 1.632 + /*out, optional */ nsIX509Cert3** aSignerCert) 1.633 +{ 1.634 + NS_ENSURE_ARG_POINTER(aJarFile); 1.635 + 1.636 + if (aZipReader) { 1.637 + *aZipReader = nullptr; 1.638 + } 1.639 + 1.640 + if (aSignerCert) { 1.641 + *aSignerCert = nullptr; 1.642 + } 1.643 + 1.644 + nsresult rv; 1.645 + 1.646 + static NS_DEFINE_CID(kZipReaderCID, NS_ZIPREADER_CID); 1.647 + nsCOMPtr<nsIZipReader> zip = do_CreateInstance(kZipReaderCID, &rv); 1.648 + NS_ENSURE_SUCCESS(rv, rv); 1.649 + 1.650 + rv = zip->Open(aJarFile); 1.651 + NS_ENSURE_SUCCESS(rv, rv); 1.652 + 1.653 + // Signature (RSA) file 1.654 + nsAutoCString sigFilename; 1.655 + ScopedAutoSECItem sigBuffer; 1.656 + rv = FindAndLoadOneEntry(zip, nsLiteralCString(JAR_RSA_SEARCH_STRING), 1.657 + sigFilename, sigBuffer, nullptr); 1.658 + if (NS_FAILED(rv)) { 1.659 + return NS_ERROR_SIGNED_JAR_NOT_SIGNED; 1.660 + } 1.661 + 1.662 + // Signature (SF) file 1.663 + nsAutoCString sfFilename; 1.664 + ScopedAutoSECItem sfBuffer; 1.665 + Digest sfCalculatedDigest; 1.666 + rv = FindAndLoadOneEntry(zip, NS_LITERAL_CSTRING(JAR_SF_SEARCH_STRING), 1.667 + sfFilename, sfBuffer, &sfCalculatedDigest); 1.668 + if (NS_FAILED(rv)) { 1.669 + return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; 1.670 + } 1.671 + 1.672 + sigBuffer.type = siBuffer; 1.673 + mozilla::pkix::ScopedCERTCertList builtChain; 1.674 + rv = VerifySignature(aTrustedRoot, sigBuffer, sfCalculatedDigest.get(), 1.675 + builtChain); 1.676 + if (NS_FAILED(rv)) { 1.677 + return rv; 1.678 + } 1.679 + 1.680 + ScopedAutoSECItem mfDigest; 1.681 + rv = ParseSF(char_ptr_cast(sfBuffer.data), mfDigest); 1.682 + if (NS_FAILED(rv)) { 1.683 + return rv; 1.684 + } 1.685 + 1.686 + // Manifest (MF) file 1.687 + nsAutoCString mfFilename; 1.688 + ScopedAutoSECItem manifestBuffer; 1.689 + Digest mfCalculatedDigest; 1.690 + rv = FindAndLoadOneEntry(zip, NS_LITERAL_CSTRING(JAR_MF_SEARCH_STRING), 1.691 + mfFilename, manifestBuffer, &mfCalculatedDigest); 1.692 + if (NS_FAILED(rv)) { 1.693 + return rv; 1.694 + } 1.695 + 1.696 + if (SECITEM_CompareItem(&mfDigest, &mfCalculatedDigest.get()) != SECEqual) { 1.697 + return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; 1.698 + } 1.699 + 1.700 + // Allocate the I/O buffer only once per JAR, instead of once per entry, in 1.701 + // order to minimize malloc/free calls and in order to avoid fragmenting 1.702 + // memory. 1.703 + ScopedAutoSECItem buf(128 * 1024); 1.704 + 1.705 + nsTHashtable<nsCStringHashKey> items; 1.706 + 1.707 + rv = ParseMF(char_ptr_cast(manifestBuffer.data), zip, items, buf); 1.708 + if (NS_FAILED(rv)) { 1.709 + return rv; 1.710 + } 1.711 + 1.712 + // Verify every entry in the file. 1.713 + nsCOMPtr<nsIUTF8StringEnumerator> entries; 1.714 + rv = zip->FindEntries(EmptyCString(), getter_AddRefs(entries)); 1.715 + if (NS_SUCCEEDED(rv) && !entries) { 1.716 + rv = NS_ERROR_UNEXPECTED; 1.717 + } 1.718 + if (NS_FAILED(rv)) { 1.719 + return rv; 1.720 + } 1.721 + 1.722 + for (;;) { 1.723 + bool hasMore; 1.724 + rv = entries->HasMore(&hasMore); 1.725 + NS_ENSURE_SUCCESS(rv, rv); 1.726 + 1.727 + if (!hasMore) { 1.728 + break; 1.729 + } 1.730 + 1.731 + nsAutoCString entryFilename; 1.732 + rv = entries->GetNext(entryFilename); 1.733 + NS_ENSURE_SUCCESS(rv, rv); 1.734 + 1.735 + PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("Verifying digests for %s", 1.736 + entryFilename.get())); 1.737 + 1.738 + // The files that comprise the signature mechanism are not covered by the 1.739 + // signature. 1.740 + // 1.741 + // XXX: This is OK for a single signature, but doesn't work for 1.742 + // multiple signatures, because the metadata for the other signatures 1.743 + // is not signed either. 1.744 + if (entryFilename == mfFilename || 1.745 + entryFilename == sfFilename || 1.746 + entryFilename == sigFilename) { 1.747 + continue; 1.748 + } 1.749 + 1.750 + if (entryFilename.Length() == 0) { 1.751 + return NS_ERROR_SIGNED_JAR_ENTRY_INVALID; 1.752 + } 1.753 + 1.754 + // Entries with names that end in "/" are directory entries, which are not 1.755 + // signed. 1.756 + // 1.757 + // XXX: As long as we don't unpack the JAR into the filesystem, the "/" 1.758 + // entries are harmless. But, it is not clear what the security 1.759 + // implications of directory entries are if/when we were to unpackage the 1.760 + // JAR into the filesystem. 1.761 + if (entryFilename[entryFilename.Length() - 1] == '/') { 1.762 + continue; 1.763 + } 1.764 + 1.765 + nsCStringHashKey * item = items.GetEntry(entryFilename); 1.766 + if (!item) { 1.767 + return NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY; 1.768 + } 1.769 + 1.770 + // Remove the item so we can check for leftover items later 1.771 + items.RemoveEntry(entryFilename); 1.772 + } 1.773 + 1.774 + // We verified that every entry that we require to be signed is signed. But, 1.775 + // were there any missing entries--that is, entries that are mentioned in the 1.776 + // manifest but missing from the archive? 1.777 + if (items.Count() != 0) { 1.778 + return NS_ERROR_SIGNED_JAR_ENTRY_MISSING; 1.779 + } 1.780 + 1.781 + // Return the reader to the caller if they want it 1.782 + if (aZipReader) { 1.783 + zip.forget(aZipReader); 1.784 + } 1.785 + 1.786 + // Return the signer's certificate to the reader if they want it. 1.787 + // XXX: We should return an nsIX509CertList with the whole validated chain, 1.788 + // but we can't do that until we switch to libpkix. 1.789 + if (aSignerCert) { 1.790 + MOZ_ASSERT(CERT_LIST_HEAD(builtChain)); 1.791 + nsCOMPtr<nsIX509Cert3> signerCert = 1.792 + nsNSSCertificate::Create(CERT_LIST_HEAD(builtChain)->cert); 1.793 + NS_ENSURE_TRUE(signerCert, NS_ERROR_OUT_OF_MEMORY); 1.794 + signerCert.forget(aSignerCert); 1.795 + } 1.796 + 1.797 + return NS_OK; 1.798 +} 1.799 + 1.800 +class OpenSignedAppFileTask MOZ_FINAL : public CryptoTask 1.801 +{ 1.802 +public: 1.803 + OpenSignedAppFileTask(AppTrustedRoot aTrustedRoot, nsIFile* aJarFile, 1.804 + nsIOpenSignedAppFileCallback* aCallback) 1.805 + : mTrustedRoot(aTrustedRoot) 1.806 + , mJarFile(aJarFile) 1.807 + , mCallback(new nsMainThreadPtrHolder<nsIOpenSignedAppFileCallback>(aCallback)) 1.808 + { 1.809 + } 1.810 + 1.811 +private: 1.812 + virtual nsresult CalculateResult() MOZ_OVERRIDE 1.813 + { 1.814 + return OpenSignedAppFile(mTrustedRoot, mJarFile, 1.815 + getter_AddRefs(mZipReader), 1.816 + getter_AddRefs(mSignerCert)); 1.817 + } 1.818 + 1.819 + // nsNSSCertificate implements nsNSSShutdownObject, so there's nothing that 1.820 + // needs to be released 1.821 + virtual void ReleaseNSSResources() { } 1.822 + 1.823 + virtual void CallCallback(nsresult rv) 1.824 + { 1.825 + (void) mCallback->OpenSignedAppFileFinished(rv, mZipReader, mSignerCert); 1.826 + } 1.827 + 1.828 + const AppTrustedRoot mTrustedRoot; 1.829 + const nsCOMPtr<nsIFile> mJarFile; 1.830 + nsMainThreadPtrHandle<nsIOpenSignedAppFileCallback> mCallback; 1.831 + nsCOMPtr<nsIZipReader> mZipReader; // out 1.832 + nsCOMPtr<nsIX509Cert3> mSignerCert; // out 1.833 +}; 1.834 + 1.835 +} // unnamed namespace 1.836 + 1.837 +NS_IMETHODIMP 1.838 +nsNSSCertificateDB::OpenSignedAppFileAsync( 1.839 + AppTrustedRoot aTrustedRoot, nsIFile* aJarFile, 1.840 + nsIOpenSignedAppFileCallback* aCallback) 1.841 +{ 1.842 + NS_ENSURE_ARG_POINTER(aJarFile); 1.843 + NS_ENSURE_ARG_POINTER(aCallback); 1.844 + RefPtr<OpenSignedAppFileTask> task(new OpenSignedAppFileTask(aTrustedRoot, 1.845 + aJarFile, 1.846 + aCallback)); 1.847 + return task->Dispatch("SignedJAR"); 1.848 +}