tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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, &param, &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 }