AppSignatureVerification.cpp (49038B)
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 "AppSignatureVerification.h" 10 #include "AppTrustDomain.h" 11 #include "CryptoTask.h" 12 #include "NSSCertDBTrustDomain.h" 13 #include "ScopedNSSTypes.h" 14 #include "SharedCertVerifier.h" 15 #include "certdb.h" 16 #include "cms.h" 17 #include "cosec.h" 18 #include "mozilla/Base64.h" 19 #include "mozilla/Casting.h" 20 #include "mozilla/glean/SecurityManagerSslMetrics.h" 21 #include "mozilla/Logging.h" 22 #include "mozilla/Preferences.h" 23 #include "mozilla/RefPtr.h" 24 #include "nsCOMPtr.h" 25 #include "nsComponentManagerUtils.h" 26 #include "nsDependentString.h" 27 #include "nsHashKeys.h" 28 #include "nsIFile.h" 29 #include "nsIInputStream.h" 30 #include "nsIStringEnumerator.h" 31 #include "nsIZipReader.h" 32 #include "nsNSSCertificate.h" 33 #include "nsNetUtil.h" 34 #include "nsProxyRelease.h" 35 #include "nsString.h" 36 #include "nsTHashtable.h" 37 #include "mozpkix/pkix.h" 38 #include "mozpkix/pkixnss.h" 39 #include "mozpkix/pkixutil.h" 40 #include "secerr.h" 41 #include "secmime.h" 42 43 using namespace mozilla::pkix; 44 using namespace mozilla; 45 using namespace mozilla::psm; 46 47 extern mozilla::LazyLogModule gPIPNSSLog; 48 49 Span<const uint8_t> GetPKCS7SignerCert( 50 NSSCMSSignerInfo* signerInfo, 51 nsTArray<Span<const uint8_t>>& collectedCerts) { 52 if (!signerInfo) { 53 return {}; 54 } 55 // The NSS APIs use the term "CMS", but since these are all signed by Mozilla 56 // infrastructure, we know they are actually PKCS7. This means that this only 57 // needs to handle issuer/serial number signer identifiers. 58 if (signerInfo->signerIdentifier.identifierType != NSSCMSSignerID_IssuerSN) { 59 return {}; 60 } 61 CERTIssuerAndSN* issuerAndSN = signerInfo->signerIdentifier.id.issuerAndSN; 62 if (!issuerAndSN) { 63 return {}; 64 } 65 Input issuer; 66 mozilla::pkix::Result result = 67 issuer.Init(issuerAndSN->derIssuer.data, issuerAndSN->derIssuer.len); 68 if (result != Success) { 69 return {}; 70 } 71 Input serialNumber; 72 result = serialNumber.Init(issuerAndSN->serialNumber.data, 73 issuerAndSN->serialNumber.len); 74 if (result != Success) { 75 return {}; 76 } 77 for (const auto& certDER : collectedCerts) { 78 Input certInput; 79 result = certInput.Init(certDER.Elements(), certDER.Length()); 80 if (result != Success) { 81 continue; // probably too big 82 } 83 // Since this only decodes the certificate and doesn't attempt to build a 84 // verified chain with it, the EndEntityOrCA parameter doesn't matter. 85 BackCert cert(certInput, EndEntityOrCA::MustBeEndEntity, nullptr); 86 result = cert.Init(); 87 if (result != Success) { 88 continue; 89 } 90 if (InputsAreEqual(issuer, cert.GetIssuer()) && 91 InputsAreEqual(serialNumber, cert.GetSerialNumber())) { 92 return certDER; 93 } 94 } 95 return {}; 96 } 97 98 NSSCMSSignedData* GetSignedDataContent(NSSCMSMessage* cmsg) { 99 NSSCMSContentInfo* cinfo = NSS_CMSMessage_ContentLevel(cmsg, 0); 100 if (!cinfo) { 101 return nullptr; 102 } 103 104 if (NSS_CMSContentInfo_GetContentTypeTag(cinfo) != 105 SEC_OID_PKCS7_SIGNED_DATA) { 106 return nullptr; 107 } 108 109 return static_cast<NSSCMSSignedData*>(NSS_CMSContentInfo_GetContent(cinfo)); 110 } 111 112 void CollectCertificates(NSSCMSSignedData* signedData, 113 nsTArray<Span<const uint8_t>>& collectedCerts) { 114 if (signedData->rawCerts) { 115 for (size_t i = 0; signedData->rawCerts[i]; ++i) { 116 Span<const uint8_t> cert(signedData->rawCerts[i]->data, 117 signedData->rawCerts[i]->len); 118 collectedCerts.AppendElement(std::move(cert)); 119 } 120 } 121 } 122 123 nsresult VerifySignatureFromCertificate(Span<const uint8_t> signerCertSpan, 124 NSSCMSSignerInfo* signerInfo, 125 SECItem* detachedDigest) { 126 // Ensure that the PKCS#7 data OID is present as the PKCS#9 contentType. 127 const char* pkcs7DataOidString = "1.2.840.113549.1.7.1"; 128 ScopedAutoSECItem pkcs7DataOid; 129 if (SEC_StringToOID(nullptr, &pkcs7DataOid, pkcs7DataOidString, 0) != 130 SECSuccess) { 131 return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING; 132 } 133 134 // NSS_CMSSignerInfo_Verify relies on NSS_CMSSignerInfo_GetSigningCertificate 135 // having been called already. This relies on the signing certificate being 136 // decoded as a CERTCertificate. 137 // This assertion should never fail, as this certificate has been 138 // successfully verified, which means it fits in the size of an unsigned int. 139 SECItem signingCertificateItem = { 140 siBuffer, const_cast<unsigned char*>(signerCertSpan.Elements()), 141 AssertedCast<unsigned int>(signerCertSpan.Length())}; 142 UniqueCERTCertificate signingCertificateHandle(CERT_NewTempCertificate( 143 CERT_GetDefaultCertDB(), &signingCertificateItem, nullptr, false, true)); 144 if (!signingCertificateHandle) { 145 return mozilla::psm::GetXPCOMFromNSSError(SEC_ERROR_PKCS7_BAD_SIGNATURE); 146 } 147 // NB: This function does not return an owning reference, unlike with many 148 // other NSS APIs. 149 if (!NSS_CMSSignerInfo_GetSigningCertificate(signerInfo, 150 CERT_GetDefaultCertDB())) { 151 return mozilla::psm::GetXPCOMFromNSSError(SEC_ERROR_PKCS7_BAD_SIGNATURE); 152 } 153 return MapSECStatus(NSS_CMSSignerInfo_Verify( 154 signerInfo, const_cast<SECItem*>(detachedDigest), &pkcs7DataOid)); 155 } 156 157 void GetAllSignerInfosForSupportedDigestAlgorithms( 158 NSSCMSSignedData* signedData, 159 /* out */ nsTArray<std::tuple<NSSCMSSignerInfo*, SECOidTag>>& signerInfos) { 160 static constexpr SECOidTag kSupportedDigestAlgorithms[] = {SEC_OID_SHA256, 161 SEC_OID_SHA1}; 162 163 int numSigners = NSS_CMSSignedData_SignerInfoCount(signedData); 164 if (numSigners < 1) { 165 return; 166 } 167 168 for (const auto& digestAlgorithm : kSupportedDigestAlgorithms) { 169 for (int i = 0; i < numSigners; i++) { 170 NSSCMSSignerInfo* signerInfo = 171 NSS_CMSSignedData_GetSignerInfo(signedData, i); 172 // NSS_CMSSignerInfo_GetDigestAlgTag isn't exported from NSS. 173 SECOidData* digestAlgOID = 174 SECOID_FindOID(&signerInfo->digestAlg.algorithm); 175 if (!digestAlgOID) { 176 continue; 177 } 178 179 if (digestAlgOID->offset == digestAlgorithm) { 180 signerInfos.AppendElement(std::make_tuple(signerInfo, digestAlgorithm)); 181 } 182 } 183 } 184 } 185 186 namespace { 187 188 // A convenient way to pair the bytes of a digest with the algorithm that 189 // purportedly produced those bytes. Only SHA-1 and SHA-256 are supported. 190 struct DigestWithAlgorithm { 191 nsresult ValidateLength() const { 192 size_t hashLen; 193 switch (mAlgorithm) { 194 case SEC_OID_SHA256: 195 hashLen = SHA256_LENGTH; 196 break; 197 case SEC_OID_SHA1: 198 hashLen = SHA1_LENGTH; 199 break; 200 default: 201 MOZ_ASSERT_UNREACHABLE( 202 "unsupported hash type in DigestWithAlgorithm::ValidateLength"); 203 return NS_ERROR_FAILURE; 204 } 205 if (mDigest.Length() != hashLen) { 206 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; 207 } 208 return NS_OK; 209 } 210 211 nsAutoCString mDigest; 212 SECOidTag mAlgorithm; 213 }; 214 215 // The digest must have a lifetime greater than or equal to the returned string. 216 inline nsDependentCSubstring DigestToDependentString( 217 nsTArray<uint8_t>& digest) { 218 return nsDependentCSubstring(BitwiseCast<char*, uint8_t*>(digest.Elements()), 219 digest.Length()); 220 } 221 222 // Reads a maximum of 8MB from a stream into the supplied buffer. 223 // The reason for the 8MB limit is because this function is used to read 224 // signature-related files and we want to avoid OOM. The uncompressed length of 225 // an entry can be hundreds of times larger than the compressed version, 226 // especially if someone has specifically crafted the entry to cause OOM or to 227 // consume massive amounts of disk space. 228 // 229 // @param stream The input stream to read from. 230 // @param buf The buffer that we read the stream into, which must have 231 // already been allocated. 232 nsresult ReadStream(const nsCOMPtr<nsIInputStream>& stream, 233 /*out*/ SECItem& buf) { 234 // The size returned by Available() might be inaccurate so we need 235 // to check that Available() matches up with the actual length of 236 // the file. 237 uint64_t length; 238 nsresult rv = stream->Available(&length); 239 if (NS_WARN_IF(NS_FAILED(rv))) { 240 return rv; 241 } 242 243 // Cap the maximum accepted size of signature-related files at 8MB (which 244 // should be much larger than necessary for our purposes) to avoid OOM. 245 static const uint32_t MAX_LENGTH = 8 * 1000 * 1000; 246 if (length > MAX_LENGTH) { 247 return NS_ERROR_FILE_TOO_BIG; 248 } 249 250 // With bug 164695 in mind we +1 to leave room for null-terminating 251 // the buffer. 252 SECITEM_AllocItem(buf, static_cast<uint32_t>(length + 1)); 253 254 // buf.len == length + 1. We attempt to read length + 1 bytes 255 // instead of length, so that we can check whether the metadata for 256 // the entry is incorrect. 257 uint32_t bytesRead; 258 rv = stream->Read(BitwiseCast<char*, unsigned char*>(buf.data), buf.len, 259 &bytesRead); 260 if (NS_WARN_IF(NS_FAILED(rv))) { 261 return rv; 262 } 263 if (bytesRead != length) { 264 return NS_ERROR_FILE_CORRUPTED; 265 } 266 267 buf.data[buf.len - 1] = 0; // null-terminate 268 269 return NS_OK; 270 } 271 272 // Finds exactly one (signature metadata) JAR entry that matches the given 273 // search pattern, and then loads it. Fails if there are no matches or if 274 // there is more than one match. If bufDigest is not null then on success 275 // bufDigest will contain the digeset of the entry using the given digest 276 // algorithm. 277 nsresult FindAndLoadOneEntry( 278 nsIZipReader* zip, const nsACString& searchPattern, 279 /*out*/ nsACString& filename, 280 /*out*/ SECItem& buf, 281 /*optional, in*/ SECOidTag digestAlgorithm = SEC_OID_SHA1, 282 /*optional, out*/ nsTArray<uint8_t>* bufDigest = nullptr) { 283 nsCOMPtr<nsIUTF8StringEnumerator> files; 284 nsresult rv = zip->FindEntries(searchPattern, getter_AddRefs(files)); 285 if (NS_FAILED(rv) || !files) { 286 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; 287 } 288 289 bool more; 290 rv = files->HasMore(&more); 291 NS_ENSURE_SUCCESS(rv, rv); 292 if (!more) { 293 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; 294 } 295 296 rv = files->GetNext(filename); 297 NS_ENSURE_SUCCESS(rv, rv); 298 299 // Check if there is more than one match, if so then error! 300 rv = files->HasMore(&more); 301 NS_ENSURE_SUCCESS(rv, rv); 302 if (more) { 303 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; 304 } 305 306 nsCOMPtr<nsIInputStream> stream; 307 rv = zip->GetInputStream(filename, getter_AddRefs(stream)); 308 NS_ENSURE_SUCCESS(rv, rv); 309 310 rv = ReadStream(stream, buf); 311 if (NS_WARN_IF(NS_FAILED(rv))) { 312 return NS_ERROR_SIGNED_JAR_ENTRY_INVALID; 313 } 314 315 if (bufDigest) { 316 rv = Digest::DigestBuf(digestAlgorithm, 317 Span<uint8_t>{buf.data, buf.len - 1}, *bufDigest); 318 NS_ENSURE_SUCCESS(rv, rv); 319 } 320 321 return NS_OK; 322 } 323 324 // Verify the digest of an entry. We avoid loading the entire entry into memory 325 // at once, which would require memory in proportion to the size of the largest 326 // entry. Instead, we require only a small, fixed amount of memory. 327 // 328 // @param stream an input stream from a JAR entry or file depending on whether 329 // it is from a signed archive or unpacked into a directory 330 // @param digestFromManifest The digest that we're supposed to check the file's 331 // contents against, from the manifest 332 // @param buf A scratch buffer that we use for doing the I/O, which must have 333 // already been allocated. The size of this buffer is the unit 334 // size of our I/O. 335 nsresult VerifyStreamContentDigest( 336 nsIInputStream* stream, const DigestWithAlgorithm& digestFromManifest, 337 SECItem& buf) { 338 MOZ_ASSERT(buf.len > 0); 339 nsresult rv = digestFromManifest.ValidateLength(); 340 if (NS_FAILED(rv)) { 341 return rv; 342 } 343 344 uint64_t len64; 345 rv = stream->Available(&len64); 346 NS_ENSURE_SUCCESS(rv, rv); 347 if (len64 > UINT32_MAX) { 348 return NS_ERROR_SIGNED_JAR_ENTRY_TOO_LARGE; 349 } 350 351 Digest digest; 352 353 rv = digest.Begin(digestFromManifest.mAlgorithm); 354 NS_ENSURE_SUCCESS(rv, rv); 355 356 uint64_t totalBytesRead = 0; 357 for (;;) { 358 uint32_t bytesRead; 359 rv = stream->Read(BitwiseCast<char*, unsigned char*>(buf.data), buf.len, 360 &bytesRead); 361 NS_ENSURE_SUCCESS(rv, rv); 362 363 if (bytesRead == 0) { 364 break; // EOF 365 } 366 367 totalBytesRead += bytesRead; 368 if (totalBytesRead >= UINT32_MAX) { 369 return NS_ERROR_SIGNED_JAR_ENTRY_TOO_LARGE; 370 } 371 372 rv = digest.Update(buf.data, bytesRead); 373 NS_ENSURE_SUCCESS(rv, rv); 374 } 375 376 if (totalBytesRead != len64) { 377 // The metadata we used for Available() doesn't match the actual size of 378 // the entry. 379 return NS_ERROR_SIGNED_JAR_ENTRY_INVALID; 380 } 381 382 // Verify that the digests match. 383 nsTArray<uint8_t> outArray; 384 rv = digest.End(outArray); 385 NS_ENSURE_SUCCESS(rv, rv); 386 387 nsDependentCSubstring digestStr(DigestToDependentString(outArray)); 388 if (!digestStr.Equals(digestFromManifest.mDigest)) { 389 return NS_ERROR_SIGNED_JAR_MODIFIED_ENTRY; 390 } 391 392 return NS_OK; 393 } 394 395 nsresult VerifyEntryContentDigest(nsIZipReader* zip, 396 const nsACString& aFilename, 397 const DigestWithAlgorithm& digestFromManifest, 398 SECItem& buf) { 399 nsCOMPtr<nsIInputStream> stream; 400 nsresult rv = zip->GetInputStream(aFilename, getter_AddRefs(stream)); 401 if (NS_FAILED(rv)) { 402 return NS_ERROR_SIGNED_JAR_ENTRY_MISSING; 403 } 404 405 return VerifyStreamContentDigest(stream, digestFromManifest, buf); 406 } 407 408 // On input, nextLineStart is the start of the current line. On output, 409 // nextLineStart is the start of the next line. 410 nsresult ReadLine(/*in/out*/ const char*& nextLineStart, 411 /*out*/ nsCString& line, bool allowContinuations = true) { 412 line.Truncate(); 413 size_t previousLength = 0; 414 size_t currentLength = 0; 415 for (;;) { 416 const char* eol = strpbrk(nextLineStart, "\r\n"); 417 418 if (!eol) { // Reached end of file before newline 419 eol = nextLineStart + strlen(nextLineStart); 420 } 421 422 previousLength = currentLength; 423 line.Append(nextLineStart, eol - nextLineStart); 424 currentLength = line.Length(); 425 426 // The spec says "No line may be longer than 72 bytes (not characters)" 427 // in its UTF8-encoded form. 428 static const size_t lineLimit = 72; 429 if (currentLength - previousLength > lineLimit) { 430 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; 431 } 432 433 // The spec says: "Implementations should support 65535-byte 434 // (not character) header values..." 435 if (currentLength > 65535) { 436 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; 437 } 438 439 if (*eol == '\r') { 440 ++eol; 441 } 442 if (*eol == '\n') { 443 ++eol; 444 } 445 446 nextLineStart = eol; 447 448 if (*eol != ' ') { 449 // not a continuation 450 return NS_OK; 451 } 452 453 // continuation 454 if (!allowContinuations) { 455 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; 456 } 457 458 ++nextLineStart; // skip space and keep appending 459 } 460 } 461 462 // The header strings are defined in the JAR specification. 463 #define JAR_MF_SEARCH_STRING "(M|/M)ETA-INF/(M|m)(ANIFEST|anifest).(MF|mf)$" 464 #define JAR_COSE_MF_SEARCH_STRING "(M|/M)ETA-INF/cose.manifest$" 465 #define JAR_SF_SEARCH_STRING "(M|/M)ETA-INF/*.(SF|sf)$" 466 #define JAR_RSA_SEARCH_STRING "(M|/M)ETA-INF/*.(RSA|rsa)$" 467 #define JAR_COSE_SEARCH_STRING "(M|/M)ETA-INF/cose.sig$" 468 #define JAR_META_DIR "META-INF" 469 #define JAR_MF_HEADER "Manifest-Version: 1.0" 470 #define JAR_SF_HEADER "Signature-Version: 1.0" 471 472 nsresult ParseAttribute(const nsAutoCString& curLine, 473 /*out*/ nsAutoCString& attrName, 474 /*out*/ nsAutoCString& attrValue) { 475 // Find the colon that separates the name from the value. 476 int32_t colonPos = curLine.FindChar(':'); 477 if (colonPos == kNotFound) { 478 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; 479 } 480 481 // set attrName to the name, skipping spaces between the name and colon 482 int32_t nameEnd = colonPos; 483 for (;;) { 484 if (nameEnd == 0) { 485 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; // colon with no name 486 } 487 if (curLine[nameEnd - 1] != ' ') break; 488 --nameEnd; 489 } 490 curLine.Left(attrName, nameEnd); 491 492 // Set attrValue to the value, skipping spaces between the colon and the 493 // value. The value may be empty. 494 int32_t valueStart = colonPos + 1; 495 int32_t curLineLength = curLine.Length(); 496 while (valueStart != curLineLength && curLine[valueStart] == ' ') { 497 ++valueStart; 498 } 499 curLine.Right(attrValue, curLineLength - valueStart); 500 501 return NS_OK; 502 } 503 504 // Parses the version line of the MF or SF header. 505 nsresult CheckManifestVersion(const char*& nextLineStart, 506 const nsACString& expectedHeader) { 507 // The JAR spec says: "Manifest-Version and Signature-Version must be first, 508 // and in exactly that case (so that they can be recognized easily as magic 509 // strings)." 510 nsAutoCString curLine; 511 nsresult rv = ReadLine(nextLineStart, curLine, false); 512 if (NS_FAILED(rv)) { 513 return rv; 514 } 515 if (!curLine.Equals(expectedHeader)) { 516 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; 517 } 518 return NS_OK; 519 } 520 521 // Parses a signature file (SF) based on the JDK 8 JAR Specification. 522 // 523 // The SF file must contain a SHA*-Digest-Manifest attribute in the main 524 // section (where the * is either 1 or 256, depending on the given digest 525 // algorithm). All other sections are ignored. This means that this will NOT 526 // parse old-style signature files that have separate digests per entry. 527 // The JDK8 x-Digest-Manifest variant is better because: 528 // 529 // (1) It allows us to follow the principle that we should minimize the 530 // processing of data that we do before we verify its signature. In 531 // particular, with the x-Digest-Manifest style, we can verify the digest 532 // of MANIFEST.MF before we parse it, which prevents malicious JARs 533 // exploiting our MANIFEST.MF parser. 534 // (2) It is more time-efficient and space-efficient to have one 535 // x-Digest-Manifest instead of multiple x-Digest values. 536 // 537 // filebuf must be null-terminated. On output, mfDigest will contain the 538 // decoded value of the appropriate SHA*-DigestManifest, if found. 539 nsresult ParseSF(const char* filebuf, SECOidTag digestAlgorithm, 540 /*out*/ nsAutoCString& mfDigest) { 541 const char* digestNameToFind = nullptr; 542 switch (digestAlgorithm) { 543 case SEC_OID_SHA256: 544 digestNameToFind = "sha256-digest-manifest"; 545 break; 546 case SEC_OID_SHA1: 547 digestNameToFind = "sha1-digest-manifest"; 548 break; 549 default: 550 MOZ_ASSERT_UNREACHABLE("bad argument to ParseSF"); 551 return NS_ERROR_FAILURE; 552 } 553 554 const char* nextLineStart = filebuf; 555 nsresult rv = 556 CheckManifestVersion(nextLineStart, nsLiteralCString(JAR_SF_HEADER)); 557 if (NS_FAILED(rv)) { 558 return rv; 559 } 560 561 for (;;) { 562 nsAutoCString curLine; 563 rv = ReadLine(nextLineStart, curLine); 564 if (NS_FAILED(rv)) { 565 return rv; 566 } 567 568 if (curLine.Length() == 0) { 569 // End of main section (blank line or end-of-file). We didn't find the 570 // SHA*-Digest-Manifest we were looking for. 571 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; 572 } 573 574 nsAutoCString attrName; 575 nsAutoCString attrValue; 576 rv = ParseAttribute(curLine, attrName, attrValue); 577 if (NS_FAILED(rv)) { 578 return rv; 579 } 580 581 if (attrName.EqualsIgnoreCase(digestNameToFind)) { 582 rv = Base64Decode(attrValue, mfDigest); 583 if (NS_FAILED(rv)) { 584 return rv; 585 } 586 587 // There could be multiple SHA*-Digest-Manifest attributes, which 588 // would be an error, but it's better to just skip any erroneous 589 // duplicate entries rather than trying to detect them, because: 590 // 591 // (1) It's simpler, and simpler generally means more secure 592 // (2) An attacker can't make us accept a JAR we would otherwise 593 // reject just by adding additional SHA*-Digest-Manifest 594 // attributes. 595 return NS_OK; 596 } 597 598 // ignore unrecognized attributes 599 } 600 601 MOZ_ASSERT_UNREACHABLE("somehow exited loop in ParseSF without returning"); 602 return NS_ERROR_FAILURE; 603 } 604 605 // Parses MANIFEST.MF. The filenames of all entries will be returned in 606 // mfItems. buf must be a pre-allocated scratch buffer that is used for doing 607 // I/O. Each file's contents are verified against the entry in the manifest with 608 // the digest algorithm that matches the given one. This algorithm comes from 609 // the signature file. If the signature file has a SHA-256 digest, then SHA-256 610 // entries must be present in the manifest file. If the signature file only has 611 // a SHA-1 digest, then only SHA-1 digests will be used in the manifest file. 612 nsresult ParseMF(const char* filebuf, nsIZipReader* zip, 613 SECOidTag digestAlgorithm, 614 /*out*/ nsTHashtable<nsCStringHashKey>& mfItems, 615 ScopedAutoSECItem& buf) { 616 const char* digestNameToFind = nullptr; 617 switch (digestAlgorithm) { 618 case SEC_OID_SHA256: 619 digestNameToFind = "sha256-digest"; 620 break; 621 case SEC_OID_SHA1: 622 digestNameToFind = "sha1-digest"; 623 break; 624 default: 625 MOZ_ASSERT_UNREACHABLE("bad argument to ParseMF"); 626 return NS_ERROR_FAILURE; 627 } 628 629 const char* nextLineStart = filebuf; 630 nsresult rv = 631 CheckManifestVersion(nextLineStart, nsLiteralCString(JAR_MF_HEADER)); 632 if (NS_FAILED(rv)) { 633 return rv; 634 } 635 636 // Skip the rest of the header section, which ends with a blank line. 637 { 638 nsAutoCString line; 639 do { 640 rv = ReadLine(nextLineStart, line); 641 if (NS_FAILED(rv)) { 642 return rv; 643 } 644 } while (line.Length() > 0); 645 646 // Manifest containing no file entries is OK, though useless. 647 if (*nextLineStart == '\0') { 648 return NS_OK; 649 } 650 } 651 652 nsAutoCString curItemName; 653 nsAutoCString digest; 654 655 for (;;) { 656 nsAutoCString curLine; 657 rv = ReadLine(nextLineStart, curLine); 658 if (NS_FAILED(rv)) { 659 return rv; 660 } 661 662 if (curLine.Length() == 0) { 663 // end of section (blank line or end-of-file) 664 665 if (curItemName.Length() == 0) { 666 // '...Each section must start with an attribute with the name as 667 // "Name",...', so every section must have a Name attribute. 668 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; 669 } 670 671 if (digest.IsEmpty()) { 672 // We require every entry to have a digest, since we require every 673 // entry to be signed and we don't allow duplicate entries. 674 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; 675 } 676 677 if (mfItems.Contains(curItemName)) { 678 // Duplicate entry 679 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; 680 } 681 682 // Verify that the entry's content digest matches the digest from this 683 // MF section. 684 DigestWithAlgorithm digestWithAlgorithm = {digest, digestAlgorithm}; 685 rv = VerifyEntryContentDigest(zip, curItemName, digestWithAlgorithm, buf); 686 if (NS_FAILED(rv)) { 687 return rv; 688 } 689 690 mfItems.PutEntry(curItemName); 691 692 if (*nextLineStart == '\0') { 693 // end-of-file 694 break; 695 } 696 697 // reset so we know we haven't encountered either of these for the next 698 // item yet. 699 curItemName.Truncate(); 700 digest.Truncate(); 701 702 continue; // skip the rest of the loop below 703 } 704 705 nsAutoCString attrName; 706 nsAutoCString attrValue; 707 rv = ParseAttribute(curLine, attrName, attrValue); 708 if (NS_FAILED(rv)) { 709 return rv; 710 } 711 712 // Lines to look for: 713 714 // (1) Digest: 715 if (attrName.EqualsIgnoreCase(digestNameToFind)) { 716 if (!digest.IsEmpty()) { // multiple SHA* digests in section 717 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; 718 } 719 720 rv = Base64Decode(attrValue, digest); 721 if (NS_FAILED(rv)) { 722 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; 723 } 724 725 continue; 726 } 727 728 // (2) Name: associates this manifest section with a file in the jar. 729 if (attrName.LowerCaseEqualsLiteral("name")) { 730 if (MOZ_UNLIKELY(curItemName.Length() > 0)) // multiple names in section 731 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; 732 733 if (MOZ_UNLIKELY(attrValue.Length() == 0)) 734 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; 735 736 curItemName = attrValue; 737 738 continue; 739 } 740 741 // (3) Magic: the only other must-understand attribute 742 if (attrName.LowerCaseEqualsLiteral("magic")) { 743 // We don't understand any magic, so we can't verify an entry that 744 // requires magic. Since we require every entry to have a valid 745 // signature, we have no choice but to reject the entry. 746 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; 747 } 748 749 // unrecognized attributes must be ignored 750 } 751 752 return NS_OK; 753 } 754 755 nsresult VerifyCertificate(Span<const uint8_t> signerCert, 756 AppTrustedRoot trustedRoot, 757 nsTArray<Span<const uint8_t>>&& collectedCerts) { 758 AppTrustDomain trustDomain(std::move(collectedCerts)); 759 nsresult rv = trustDomain.SetTrustedRoot(trustedRoot); 760 if (NS_FAILED(rv)) { 761 return rv; 762 } 763 Input certDER; 764 mozilla::pkix::Result result = 765 certDER.Init(signerCert.Elements(), signerCert.Length()); 766 if (result != Success) { 767 return mozilla::psm::GetXPCOMFromNSSError(MapResultToPRErrorCode(result)); 768 } 769 770 result = BuildCertChain( 771 trustDomain, certDER, Now(), EndEntityOrCA::MustBeEndEntity, 772 KeyUsage::digitalSignature, KeyPurposeId::id_kp_codeSigning, 773 CertPolicyId::anyPolicy, nullptr /*stapledOCSPResponse*/); 774 if (result == mozilla::pkix::Result::ERROR_EXPIRED_CERTIFICATE || 775 result == mozilla::pkix::Result::ERROR_NOT_YET_VALID_CERTIFICATE) { 776 // For code-signing you normally need trusted 3rd-party timestamps to 777 // handle expiration properly. The signer could always mess with their 778 // system clock so you can't trust the certificate was un-expired when 779 // the signing took place. The choice is either to ignore expiration 780 // or to enforce expiration at time of use. The latter leads to the 781 // user-hostile result that perfectly good code stops working. 782 // 783 // Our package format doesn't support timestamps (nor do we have a 784 // trusted 3rd party timestamper), but since we sign all of our apps and 785 // add-ons ourselves we can trust ourselves not to mess with the clock 786 // on the signing systems. We also have a revocation mechanism if we 787 // need it. Under these conditions it's OK to ignore cert errors related 788 // to time validity (expiration and "not yet valid"). 789 // 790 // This is an invalid approach if 791 // * we issue certs to let others sign their own packages 792 // * mozilla::pkix returns "expired" when there are "worse" problems 793 // with the certificate or chain. 794 // (see bug 1267318) 795 result = Success; 796 } 797 if (result != Success) { 798 return mozilla::psm::GetXPCOMFromNSSError(MapResultToPRErrorCode(result)); 799 } 800 801 return NS_OK; 802 } 803 804 nsresult VerifySignature(AppTrustedRoot trustedRoot, const SECItem& buffer, 805 nsTArray<uint8_t>& detachedSHA1Digest, 806 nsTArray<uint8_t>& detachedSHA256Digest, 807 /*out*/ SECOidTag& digestAlgorithm, 808 /*out*/ nsTArray<uint8_t>& signerCert) { 809 if (NS_WARN_IF(!buffer.data || buffer.len == 0 || 810 detachedSHA1Digest.Length() == 0 || 811 detachedSHA256Digest.Length() == 0)) { 812 return NS_ERROR_INVALID_ARG; 813 } 814 815 UniqueNSSCMSMessage cmsMsg(NSS_CMSMessage_CreateFromDER( 816 const_cast<SECItem*>(&buffer), nullptr, nullptr, nullptr, nullptr, 817 nullptr, nullptr)); 818 if (!cmsMsg) { 819 return NS_ERROR_CMS_VERIFY_NOT_SIGNED; 820 } 821 822 if (!NSS_CMSMessage_IsSigned(cmsMsg.get())) { 823 return NS_ERROR_CMS_VERIFY_NOT_SIGNED; 824 } 825 826 NSSCMSSignedData* signedData = GetSignedDataContent(cmsMsg.get()); 827 if (!signedData) { 828 return NS_ERROR_CMS_VERIFY_NO_CONTENT_INFO; 829 } 830 831 nsTArray<Span<const uint8_t>> collectedCerts; 832 CollectCertificates(signedData, collectedCerts); 833 if (collectedCerts.Length() == 0) { 834 return NS_ERROR_CMS_VERIFY_NOCERT; 835 } 836 837 nsTArray<std::tuple<NSSCMSSignerInfo*, SECOidTag>> signerInfos; 838 839 GetAllSignerInfosForSupportedDigestAlgorithms(signedData, signerInfos); 840 841 if (signerInfos.Length() == 0) { 842 return NS_ERROR_CMS_VERIFY_NOT_SIGNED; 843 } 844 845 // The signerInfo with the hash function with the highest priority is used 846 NSSCMSSignerInfo* signerInfo = std::get<0>(signerInfos[0]); 847 digestAlgorithm = std::get<1>(signerInfos[0]); 848 849 nsTArray<uint8_t>* tmpDetachedDigest; 850 if (digestAlgorithm == SEC_OID_SHA256) { 851 tmpDetachedDigest = &detachedSHA256Digest; 852 } else if (digestAlgorithm == SEC_OID_SHA1) { 853 tmpDetachedDigest = &detachedSHA1Digest; 854 } else { 855 return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING; 856 } 857 858 SECItem detachedDigest = { 859 siBuffer, tmpDetachedDigest->Elements(), 860 static_cast<unsigned int>(tmpDetachedDigest->Length())}; 861 862 // Get the certificate that issued the PKCS7 signature. 863 Span<const uint8_t> signerCertSpan = 864 GetPKCS7SignerCert(signerInfo, collectedCerts); 865 if (signerCertSpan.IsEmpty()) { 866 return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING; 867 } 868 869 nsresult rv = 870 VerifyCertificate(signerCertSpan, trustedRoot, std::move(collectedCerts)); 871 if (NS_FAILED(rv)) { 872 return rv; 873 } 874 signerCert.Clear(); 875 signerCert.AppendElements(signerCertSpan); 876 877 return VerifySignatureFromCertificate(signerCertSpan, signerInfo, 878 &detachedDigest); 879 } 880 881 class CoseVerificationContext { 882 public: 883 explicit CoseVerificationContext(AppTrustedRoot aTrustedRoot) 884 : mTrustedRoot(aTrustedRoot) {} 885 ~CoseVerificationContext() = default; 886 887 AppTrustedRoot GetTrustedRoot() { return mTrustedRoot; } 888 void SetCert(Span<const uint8_t> certDER) { 889 mCertDER.Clear(); 890 mCertDER.AppendElements(certDER); 891 } 892 893 nsTArray<uint8_t> TakeCert() { return std::move(mCertDER); } 894 895 private: 896 AppTrustedRoot mTrustedRoot; 897 nsTArray<uint8_t> mCertDER; 898 }; 899 900 // Verification function called from cose-rust. 901 // Returns true if everything goes well and the signature and certificate chain 902 // are good, false in any other case. 903 bool CoseVerificationCallback(const uint8_t* aPayload, size_t aPayloadLen, 904 const uint8_t** aCertChain, size_t aCertChainLen, 905 const size_t* aCertsLen, const uint8_t* aEECert, 906 size_t aEECertLen, const uint8_t* aSignature, 907 size_t aSignatureLen, uint8_t aSignatureAlgorithm, 908 void* ctx) { 909 if (!ctx || !aPayload || !aEECert || !aSignature) { 910 return false; 911 } 912 // The ctx here is a pointer to a CoseVerificationContext object 913 CoseVerificationContext* context = static_cast<CoseVerificationContext*>(ctx); 914 AppTrustedRoot aTrustedRoot = context->GetTrustedRoot(); 915 916 CK_MECHANISM_TYPE mechanism; 917 SECOidTag oid; 918 uint32_t hash_length; 919 SECItem param = {siBuffer, nullptr, 0}; 920 switch (aSignatureAlgorithm) { 921 case ES256: 922 mechanism = CKM_ECDSA; 923 oid = SEC_OID_SHA256; 924 hash_length = SHA256_LENGTH; 925 break; 926 case ES384: 927 mechanism = CKM_ECDSA; 928 oid = SEC_OID_SHA384; 929 hash_length = SHA384_LENGTH; 930 break; 931 case ES512: 932 mechanism = CKM_ECDSA; 933 oid = SEC_OID_SHA512; 934 hash_length = SHA512_LENGTH; 935 break; 936 default: 937 return false; 938 } 939 940 uint8_t hashBuf[HASH_LENGTH_MAX]; 941 SECStatus rv = PK11_HashBuf(oid, hashBuf, aPayload, aPayloadLen); 942 if (rv != SECSuccess) { 943 return false; 944 } 945 SECItem hashItem = {siBuffer, hashBuf, hash_length}; 946 Input certInput; 947 if (certInput.Init(aEECert, aEECertLen) != Success) { 948 return false; 949 } 950 // Since this only decodes the certificate and doesn't attempt to build a 951 // verified chain with it, the EndEntityOrCA parameter doesn't matter. 952 BackCert backCert(certInput, EndEntityOrCA::MustBeEndEntity, nullptr); 953 if (backCert.Init() != Success) { 954 return false; 955 } 956 Input spkiInput = backCert.GetSubjectPublicKeyInfo(); 957 SECItem spkiItem = {siBuffer, const_cast<uint8_t*>(spkiInput.UnsafeGetData()), 958 spkiInput.GetLength()}; 959 UniqueCERTSubjectPublicKeyInfo spki( 960 SECKEY_DecodeDERSubjectPublicKeyInfo(&spkiItem)); 961 if (!spki) { 962 return false; 963 } 964 UniqueSECKEYPublicKey key(SECKEY_ExtractPublicKey(spki.get())); 965 SECItem signatureItem = {siBuffer, const_cast<uint8_t*>(aSignature), 966 static_cast<unsigned int>(aSignatureLen)}; 967 rv = PK11_VerifyWithMechanism(key.get(), mechanism, ¶m, &signatureItem, 968 &hashItem, nullptr); 969 if (rv != SECSuccess) { 970 return false; 971 } 972 973 nsTArray<Span<const uint8_t>> collectedCerts; 974 for (size_t i = 0; i < aCertChainLen; ++i) { 975 Span<const uint8_t> cert(aCertChain[i], aCertsLen[i]); 976 collectedCerts.AppendElement(std::move(cert)); 977 } 978 979 Span<const uint8_t> certSpan = {aEECert, aEECertLen}; 980 nsresult nrv = 981 VerifyCertificate(certSpan, aTrustedRoot, std::move(collectedCerts)); 982 bool result = true; 983 if (NS_FAILED(nrv)) { 984 result = false; 985 } 986 987 // Passing back the signing certificate in form of the DER cert. 988 context->SetCert(certSpan); 989 if (NS_FAILED(nrv)) { 990 result = false; 991 } 992 993 return result; 994 } 995 996 nsresult VerifyAppManifest(SECOidTag aDigestToUse, nsCOMPtr<nsIZipReader> aZip, 997 nsTHashtable<nsCStringHashKey>& aIgnoredFiles, 998 const SECItem& aManifestBuffer) { 999 // Allocate the I/O buffer only once per JAR, instead of once per entry, in 1000 // order to minimize malloc/free calls and in order to avoid fragmenting 1001 // memory. 1002 ScopedAutoSECItem buf(128 * 1024); 1003 1004 nsTHashtable<nsCStringHashKey> items; 1005 1006 nsresult rv = 1007 ParseMF(BitwiseCast<char*, unsigned char*>(aManifestBuffer.data), aZip, 1008 aDigestToUse, items, buf); 1009 if (NS_FAILED(rv)) { 1010 return rv; 1011 } 1012 1013 // Verify every entry in the file. 1014 nsCOMPtr<nsIUTF8StringEnumerator> entries; 1015 rv = aZip->FindEntries(""_ns, getter_AddRefs(entries)); 1016 if (NS_FAILED(rv)) { 1017 return rv; 1018 } 1019 if (!entries) { 1020 return NS_ERROR_UNEXPECTED; 1021 } 1022 1023 for (;;) { 1024 bool hasMore; 1025 rv = entries->HasMore(&hasMore); 1026 NS_ENSURE_SUCCESS(rv, rv); 1027 1028 if (!hasMore) { 1029 break; 1030 } 1031 1032 nsAutoCString entryFilename; 1033 rv = entries->GetNext(entryFilename); 1034 NS_ENSURE_SUCCESS(rv, rv); 1035 1036 MOZ_LOG(gPIPNSSLog, LogLevel::Debug, 1037 ("Verifying digests for %s", entryFilename.get())); 1038 1039 if (entryFilename.Length() == 0) { 1040 return NS_ERROR_SIGNED_JAR_ENTRY_INVALID; 1041 } 1042 1043 // The files that comprise the signature mechanism are not covered by the 1044 // signature. Ignore these files. 1045 if (aIgnoredFiles.Contains(entryFilename)) { 1046 continue; 1047 } 1048 1049 // Entries with names that end in "/" are directory entries, which are not 1050 // signed. 1051 // 1052 // Since bug 1415991 we don't support unpacked JARs. The "/" entries are 1053 // therefore harmless. 1054 if (entryFilename.Last() == '/') { 1055 continue; 1056 } 1057 1058 nsCStringHashKey* item = items.GetEntry(entryFilename); 1059 if (!item) { 1060 return NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY; 1061 } 1062 1063 // Remove the item so we can check for leftover items later 1064 items.RemoveEntry(item); 1065 } 1066 1067 // We verified that every entry that we require to be signed is signed. But, 1068 // were there any missing entries--that is, entries that are mentioned in the 1069 // manifest but missing from the archive? 1070 if (items.Count() != 0) { 1071 return NS_ERROR_SIGNED_JAR_ENTRY_MISSING; 1072 } 1073 1074 return NS_OK; 1075 } 1076 1077 // This corresponds to the preference "security.signed_app_signatures.policy". 1078 // The lowest order bit determines which PKCS#7 algorithms are accepted. 1079 // xxx_0_: SHA-1 and/or SHA-256 PKCS#7 allowed 1080 // xxx_1_: SHA-256 PKCS#7 allowed 1081 // The next two bits determine whether COSE is required and PKCS#7 is allowed 1082 // x_00_x: COSE disabled, ignore files, PKCS#7 must verify 1083 // x_01_x: COSE is verified if present, PKCS#7 must verify 1084 // x_10_x: COSE is required, PKCS#7 must verify if present 1085 // x_11_x: COSE is required, PKCS#7 disabled (fail when present) 1086 class SignaturePolicy { 1087 public: 1088 explicit SignaturePolicy(int32_t preference) 1089 : mProcessCose(true), 1090 mCoseRequired(false), 1091 mProcessPK7(true), 1092 mPK7Required(true), 1093 mSHA1Allowed(true), 1094 mSHA256Allowed(true) { 1095 mCoseRequired = (preference & 0b100) != 0; 1096 mProcessCose = (preference & 0b110) != 0; 1097 mPK7Required = (preference & 0b100) == 0; 1098 mProcessPK7 = (preference & 0b110) != 0b110; 1099 if ((preference & 0b1) == 0) { 1100 mSHA1Allowed = true; 1101 mSHA256Allowed = true; 1102 } else { 1103 mSHA1Allowed = false; 1104 mSHA256Allowed = true; 1105 } 1106 } 1107 ~SignaturePolicy() = default; 1108 bool ProcessCOSE() { return mProcessCose; } 1109 bool COSERequired() { return mCoseRequired; } 1110 bool PK7Required() { return mPK7Required; } 1111 bool ProcessPK7() { return mProcessPK7; } 1112 bool IsPK7HashAllowed(SECOidTag aHashAlg) { 1113 if (aHashAlg == SEC_OID_SHA256 && mSHA256Allowed) { 1114 return true; 1115 } 1116 if (aHashAlg == SEC_OID_SHA1 && mSHA1Allowed) { 1117 return true; 1118 } 1119 return false; 1120 } 1121 1122 private: 1123 bool mProcessCose; 1124 bool mCoseRequired; 1125 bool mProcessPK7; 1126 bool mPK7Required; 1127 bool mSHA1Allowed; 1128 bool mSHA256Allowed; 1129 }; 1130 1131 nsresult VerifyCOSESignature(AppTrustedRoot aTrustedRoot, nsIZipReader* aZip, 1132 SignaturePolicy& aPolicy, 1133 nsTHashtable<nsCStringHashKey>& aIgnoredFiles, 1134 /* out */ bool& aVerified, 1135 /* out */ nsTArray<uint8_t>& aCoseCertDER) { 1136 NS_ENSURE_ARG_POINTER(aZip); 1137 bool required = aPolicy.COSERequired(); 1138 aVerified = false; 1139 1140 // Read COSE signature file. 1141 nsAutoCString coseFilename; 1142 ScopedAutoSECItem coseBuffer; 1143 nsresult rv = FindAndLoadOneEntry( 1144 aZip, nsLiteralCString(JAR_COSE_SEARCH_STRING), coseFilename, coseBuffer); 1145 if (NS_FAILED(rv)) { 1146 return required ? NS_ERROR_SIGNED_JAR_WRONG_SIGNATURE : NS_OK; 1147 } 1148 1149 // Verify COSE signature. 1150 nsAutoCString mfFilename; 1151 ScopedAutoSECItem manifestBuffer; 1152 rv = FindAndLoadOneEntry(aZip, nsLiteralCString(JAR_COSE_MF_SEARCH_STRING), 1153 mfFilename, manifestBuffer); 1154 if (NS_FAILED(rv)) { 1155 return required ? NS_ERROR_SIGNED_JAR_WRONG_SIGNATURE : rv; 1156 } 1157 MOZ_ASSERT(manifestBuffer.len >= 1); 1158 MOZ_ASSERT(coseBuffer.len >= 1); 1159 CoseVerificationContext context(aTrustedRoot); 1160 bool coseVerification = verify_cose_signature_ffi( 1161 manifestBuffer.data, manifestBuffer.len - 1, coseBuffer.data, 1162 coseBuffer.len - 1, &context, CoseVerificationCallback); 1163 if (!coseVerification) { 1164 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; 1165 } 1166 // CoseVerificationCallback sets the context certificate to the first cert 1167 // it encounters. 1168 aCoseCertDER = context.TakeCert(); 1169 1170 // aIgnoredFiles contains the PKCS#7 manifest and signature files iff the 1171 // PKCS#7 verification was successful. 1172 aIgnoredFiles.PutEntry(mfFilename); 1173 aIgnoredFiles.PutEntry(coseFilename); 1174 rv = VerifyAppManifest(SEC_OID_SHA256, aZip, aIgnoredFiles, manifestBuffer); 1175 if (NS_FAILED(rv)) { 1176 return rv; 1177 } 1178 1179 aVerified = true; 1180 return NS_OK; 1181 } 1182 1183 nsresult VerifyPK7Signature( 1184 AppTrustedRoot aTrustedRoot, nsIZipReader* aZip, SignaturePolicy& aPolicy, 1185 /* out */ nsTHashtable<nsCStringHashKey>& aIgnoredFiles, 1186 /* out */ bool& aVerified, 1187 /* out */ nsTArray<uint8_t>& aSignerCert, 1188 /* out */ SECOidTag& aHashAlgorithm) { 1189 NS_ENSURE_ARG_POINTER(aZip); 1190 bool required = aPolicy.PK7Required(); 1191 aVerified = false; 1192 1193 // Signature (RSA) file 1194 nsAutoCString sigFilename; 1195 ScopedAutoSECItem sigBuffer; 1196 nsresult rv = FindAndLoadOneEntry( 1197 aZip, nsLiteralCString(JAR_RSA_SEARCH_STRING), sigFilename, sigBuffer); 1198 if (NS_FAILED(rv)) { 1199 return required ? NS_ERROR_SIGNED_JAR_NOT_SIGNED : NS_OK; 1200 } 1201 1202 // Signature (SF) file 1203 nsAutoCString sfFilename; 1204 ScopedAutoSECItem sfBuffer; 1205 rv = FindAndLoadOneEntry(aZip, nsLiteralCString(JAR_SF_SEARCH_STRING), 1206 sfFilename, sfBuffer); 1207 if (NS_FAILED(rv)) { 1208 return required ? NS_ERROR_SIGNED_JAR_MANIFEST_INVALID : NS_OK; 1209 } 1210 1211 // Calculate both the SHA-1 and SHA-256 hashes of the signature file - we 1212 // don't know what algorithm the PKCS#7 signature used. 1213 nsTArray<uint8_t> sfCalculatedSHA1Digest; 1214 rv = Digest::DigestBuf(SEC_OID_SHA1, sfBuffer.data, sfBuffer.len - 1, 1215 sfCalculatedSHA1Digest); 1216 if (NS_FAILED(rv)) { 1217 return rv; 1218 } 1219 1220 nsTArray<uint8_t> sfCalculatedSHA256Digest; 1221 rv = Digest::DigestBuf(SEC_OID_SHA256, sfBuffer.data, sfBuffer.len - 1, 1222 sfCalculatedSHA256Digest); 1223 if (NS_FAILED(rv)) { 1224 return rv; 1225 } 1226 1227 // Verify PKCS#7 signature. 1228 // If we get here, the signature has to verify even if PKCS#7 is not required. 1229 sigBuffer.type = siBuffer; 1230 SECOidTag digestToUse; 1231 rv = VerifySignature(aTrustedRoot, sigBuffer, sfCalculatedSHA1Digest, 1232 sfCalculatedSHA256Digest, digestToUse, aSignerCert); 1233 if (NS_FAILED(rv)) { 1234 return rv; 1235 } 1236 1237 // Check the digest used for the signature against the policy. 1238 if (!aPolicy.IsPK7HashAllowed(digestToUse)) { 1239 return NS_ERROR_SIGNED_JAR_WRONG_SIGNATURE; 1240 } 1241 1242 nsAutoCString mfDigest; 1243 rv = ParseSF(BitwiseCast<char*, unsigned char*>(sfBuffer.data), digestToUse, 1244 mfDigest); 1245 if (NS_FAILED(rv)) { 1246 return rv; 1247 } 1248 1249 // Read PK7 manifest (MF) file. 1250 ScopedAutoSECItem manifestBuffer; 1251 nsTArray<uint8_t> digestArray; 1252 nsAutoCString mfFilename; 1253 rv = FindAndLoadOneEntry(aZip, nsLiteralCString(JAR_MF_SEARCH_STRING), 1254 mfFilename, manifestBuffer, digestToUse, 1255 &digestArray); 1256 if (NS_FAILED(rv)) { 1257 return rv; 1258 } 1259 1260 nsDependentCSubstring calculatedDigest( 1261 BitwiseCast<char*, uint8_t*>(digestArray.Elements()), 1262 digestArray.Length()); 1263 if (!mfDigest.Equals(calculatedDigest)) { 1264 return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; 1265 } 1266 1267 // Verify PKCS7 manifest file hashes. 1268 aIgnoredFiles.PutEntry(sfFilename); 1269 aIgnoredFiles.PutEntry(sigFilename); 1270 aIgnoredFiles.PutEntry(mfFilename); 1271 rv = VerifyAppManifest(digestToUse, aZip, aIgnoredFiles, manifestBuffer); 1272 if (NS_FAILED(rv)) { 1273 aIgnoredFiles.Clear(); 1274 return rv; 1275 } 1276 1277 aVerified = true; 1278 aHashAlgorithm = digestToUse; 1279 return NS_OK; 1280 } 1281 1282 class AppSignatureInfo final : public nsIAppSignatureInfo { 1283 public: 1284 NS_DECL_THREADSAFE_ISUPPORTS 1285 1286 AppSignatureInfo(RefPtr<nsIX509Cert>&& signerCert, 1287 nsIAppSignatureInfo::SignatureAlgorithm signatureAlgorithm) 1288 : mSignerCert(std::move(signerCert)), 1289 mSignatureAlgorithm(signatureAlgorithm) {} 1290 1291 NS_IMETHODIMP GetSignerCert(nsIX509Cert** signerCert) override { 1292 *signerCert = do_AddRef(mSignerCert).take(); 1293 return NS_OK; 1294 } 1295 1296 NS_IMETHODIMP GetSignatureAlgorithm( 1297 nsIAppSignatureInfo::SignatureAlgorithm* signatureAlgorithm) override { 1298 *signatureAlgorithm = mSignatureAlgorithm; 1299 return NS_OK; 1300 } 1301 1302 private: 1303 ~AppSignatureInfo() = default; 1304 1305 RefPtr<nsIX509Cert> mSignerCert; 1306 nsIAppSignatureInfo::SignatureAlgorithm mSignatureAlgorithm; 1307 }; 1308 1309 NS_IMPL_ISUPPORTS(AppSignatureInfo, nsIAppSignatureInfo) 1310 1311 nsresult OpenSignedAppFile( 1312 AppTrustedRoot aTrustedRoot, nsIFile* aJarFile, SignaturePolicy aPolicy, 1313 /* out */ nsIZipReader** aZipReader, 1314 /* out */ nsTArray<RefPtr<nsIAppSignatureInfo>>& aSignatureInfos) { 1315 NS_ENSURE_ARG_POINTER(aJarFile); 1316 1317 if (aZipReader) { 1318 *aZipReader = nullptr; 1319 } 1320 1321 aSignatureInfos.Clear(); 1322 1323 nsresult rv; 1324 1325 static NS_DEFINE_CID(kZipReaderCID, NS_ZIPREADER_CID); 1326 nsCOMPtr<nsIZipReader> zip = do_CreateInstance(kZipReaderCID, &rv); 1327 NS_ENSURE_SUCCESS(rv, rv); 1328 1329 rv = zip->Open(aJarFile); 1330 NS_ENSURE_SUCCESS(rv, rv); 1331 1332 nsTHashtable<nsCStringHashKey> ignoredFiles; 1333 bool pk7Verified = false; 1334 nsTArray<uint8_t> pkcs7CertDER; 1335 SECOidTag pkcs7HashAlgorithm = SEC_OID_UNKNOWN; 1336 bool coseVerified = false; 1337 nsTArray<uint8_t> coseCertDER; 1338 1339 // First we have to verify the PKCS#7 signature if there is one. 1340 // This signature covers all files (except for the signature files itself), 1341 // including the COSE signature files. Only when this verification is 1342 // successful the respective files will be ignored in the subsequent COSE 1343 // signature verification. 1344 if (aPolicy.ProcessPK7()) { 1345 rv = VerifyPK7Signature(aTrustedRoot, zip, aPolicy, ignoredFiles, 1346 pk7Verified, pkcs7CertDER, pkcs7HashAlgorithm); 1347 if (NS_FAILED(rv)) { 1348 return rv; 1349 } 1350 } 1351 1352 if (aPolicy.ProcessCOSE()) { 1353 rv = VerifyCOSESignature(aTrustedRoot, zip, aPolicy, ignoredFiles, 1354 coseVerified, coseCertDER); 1355 if (NS_FAILED(rv)) { 1356 return rv; 1357 } 1358 } 1359 1360 // Bits 1 and 2 1361 // 00 = Didn't Process PKCS#7 signatures 1362 // 01 = Processed but no valid cert or signature 1363 // 10 = Processed and valid cert found, but addon didn't match manifest 1364 // 11 = Processed and valid. 1365 // Bits 3 and 4 are the same but for COSE. 1366 uint32_t bucket = 0; 1367 bucket += aPolicy.ProcessCOSE(); 1368 bucket += !coseCertDER.IsEmpty(); 1369 bucket += coseVerified; 1370 bucket <<= 2; 1371 bucket += aPolicy.ProcessPK7(); 1372 bucket += !pkcs7CertDER.IsEmpty(); 1373 bucket += pk7Verified; 1374 glean::security::addon_signature_verification_status.AccumulateSingleSample( 1375 bucket); 1376 1377 if ((aPolicy.PK7Required() && !pk7Verified) || 1378 (aPolicy.COSERequired() && !coseVerified)) { 1379 return NS_ERROR_SIGNED_JAR_WRONG_SIGNATURE; 1380 } 1381 1382 // Return the reader to the caller if they want it 1383 if (aZipReader) { 1384 zip.forget(aZipReader); 1385 } 1386 1387 // Return the signature information (a list of signing certificate and 1388 // algorithm pairs). If present, the COSE signature will be first, followed 1389 // by any PKCS7 signatures. 1390 if (coseVerified && !coseCertDER.IsEmpty()) { 1391 RefPtr<nsIX509Cert> signerCert( 1392 new nsNSSCertificate(std::move(coseCertDER))); 1393 aSignatureInfos.AppendElement(new AppSignatureInfo( 1394 std::move(signerCert), 1395 nsIAppSignatureInfo::SignatureAlgorithm::COSE_WITH_SHA256)); 1396 } 1397 if (pk7Verified && !pkcs7CertDER.IsEmpty()) { 1398 RefPtr<nsIX509Cert> signerCert( 1399 new nsNSSCertificate(std::move(pkcs7CertDER))); 1400 nsIAppSignatureInfo::SignatureAlgorithm signatureAlgorithm; 1401 switch (pkcs7HashAlgorithm) { 1402 case SEC_OID_SHA1: 1403 signatureAlgorithm = 1404 nsIAppSignatureInfo::SignatureAlgorithm::PKCS7_WITH_SHA1; 1405 break; 1406 case SEC_OID_SHA256: 1407 signatureAlgorithm = 1408 nsIAppSignatureInfo::SignatureAlgorithm::PKCS7_WITH_SHA256; 1409 break; 1410 default: 1411 return NS_ERROR_FAILURE; 1412 } 1413 aSignatureInfos.AppendElement( 1414 new AppSignatureInfo(std::move(signerCert), signatureAlgorithm)); 1415 } 1416 1417 return NS_OK; 1418 } 1419 1420 class OpenSignedAppFileTask final : public CryptoTask { 1421 public: 1422 OpenSignedAppFileTask(AppTrustedRoot aTrustedRoot, nsIFile* aJarFile, 1423 SignaturePolicy aPolicy, 1424 nsIOpenSignedAppFileCallback* aCallback) 1425 : mTrustedRoot(aTrustedRoot), 1426 mJarFile(aJarFile), 1427 mPolicy(aPolicy), 1428 mCallback(new nsMainThreadPtrHolder<nsIOpenSignedAppFileCallback>( 1429 "OpenSignedAppFileTask::mCallback", aCallback)) {} 1430 1431 private: 1432 virtual nsresult CalculateResult() override { 1433 return OpenSignedAppFile(mTrustedRoot, mJarFile, mPolicy, 1434 getter_AddRefs(mZipReader), mSignatureInfos); 1435 } 1436 1437 virtual void CallCallback(nsresult rv) override { 1438 (void)mCallback->OpenSignedAppFileFinished(rv, mZipReader, mSignatureInfos); 1439 } 1440 1441 const AppTrustedRoot mTrustedRoot; 1442 const nsCOMPtr<nsIFile> mJarFile; 1443 const SignaturePolicy mPolicy; 1444 nsMainThreadPtrHandle<nsIOpenSignedAppFileCallback> mCallback; 1445 nsCOMPtr<nsIZipReader> mZipReader; // out 1446 nsTArray<RefPtr<nsIAppSignatureInfo>> mSignatureInfos; // out 1447 }; 1448 1449 static const int32_t sDefaultSignaturePolicy = 0b10; 1450 1451 } // unnamed namespace 1452 1453 NS_IMETHODIMP 1454 nsNSSCertificateDB::OpenSignedAppFileAsync( 1455 AppTrustedRoot aTrustedRoot, nsIFile* aJarFile, 1456 nsIOpenSignedAppFileCallback* aCallback) { 1457 NS_ENSURE_ARG_POINTER(aJarFile); 1458 NS_ENSURE_ARG_POINTER(aCallback); 1459 if (!NS_IsMainThread()) { 1460 return NS_ERROR_NOT_SAME_THREAD; 1461 } 1462 int32_t policyInt = 1463 Preferences::GetInt("security.signed_app_signatures.policy", 1464 static_cast<int32_t>(sDefaultSignaturePolicy)); 1465 SignaturePolicy policy(policyInt); 1466 RefPtr<OpenSignedAppFileTask> task( 1467 new OpenSignedAppFileTask(aTrustedRoot, aJarFile, policy, aCallback)); 1468 return task->Dispatch(); 1469 }