bug 1357815 - 3/4: support SHA256 in PKCS#7 signatures on add-ons r?jcj,dveditz draft
authorDavid Keeler <dkeeler@mozilla.com>
Tue, 24 Oct 2017 15:27:53 -0700
changeset 689671 0ce20c460403e496d0672ccc7702365519b9df46
parent 689670 f6120ea5356f123644d0d3b2c480d0ecc4190f55
child 689672 978d419fe840806ccdba7bb833cf8f6421faae24
push id87085
push userbmo:dkeeler@mozilla.com
push dateTue, 31 Oct 2017 21:34:47 +0000
reviewersjcj, dveditz
bugs1357815
milestone58.0a1
bug 1357815 - 3/4: support SHA256 in PKCS#7 signatures on add-ons r?jcj,dveditz As a result of this patch, the hash algorithm used in add-on signature verification will come from the PKCS#7 signature. If SHA-256 is present, it will be used. SHA-1 is used as a fallback. Otherwise, the signature is invalid. This means that, for example, if the PKCS#7 signature only has SHA-1 but there are SHA-256 hashes in the signature file and/or manifest file, only the SHA-1 hashes in the signature file and manifest file will be used, if they are present (and verification will fail if they are not present). Similarly, if the PKCS#7 signature has SHA-256, there must be SHA-256 hashes in the signature file and manifest file (even if SHA-1 is also present in the PKCS#7 signature). MozReview-Commit-ID: K3OQEpIrnUW
js/xpconnect/src/xpc.msg
security/apps/AppSignatureVerification.cpp
security/manager/ssl/tests/unit/pycms.py
security/manager/ssl/tests/unit/sign_app.py
security/manager/ssl/tests/unit/test_signed_apps.js
security/manager/ssl/tests/unit/test_signed_apps/empty_signerInfos.zip
security/manager/ssl/tests/unit/test_signed_apps/moz.build
security/manager/ssl/tests/unit/test_signed_apps/sha1_and_sha256_manifest_sha1_signature_file.zip
security/manager/ssl/tests/unit/test_signed_apps/sha1_and_sha256_manifest_sha256_signature_file.zip
security/manager/ssl/tests/unit/test_signed_apps/sha1_manifest_sha1_and_sha256_signature_file.zip
security/manager/ssl/tests/unit/test_signed_apps/sha256_manifest_sha1_and_sha256_signature_file.zip
security/manager/ssl/tests/unit/test_signed_apps/signed_app.zip
security/manager/ssl/tests/unit/test_signed_apps/signed_app_sha1_and_sha256.zip
security/manager/ssl/tests/unit/test_signed_apps/signed_app_sha256.zip
security/manager/ssl/tests/unit/test_signed_apps/signed_app_sha256_manifest.zip
security/manager/ssl/tests/unit/test_signed_apps/signed_app_sha256_signature_file.zip
security/manager/ssl/tests/unit/test_signed_apps/unknown_issuer_app.zip
security/manager/ssl/tests/unit/test_signed_apps/unsigned_app.zip
--- a/js/xpconnect/src/xpc.msg
+++ b/js/xpconnect/src/xpc.msg
@@ -198,16 +198,17 @@ XPC_MSG_DEF(NS_ERROR_ILLEGAL_INPUT      
 XPC_MSG_DEF(NS_ERROR_SIGNED_JAR_NOT_SIGNED          , "The JAR is not signed.")
 XPC_MSG_DEF(NS_ERROR_SIGNED_JAR_MODIFIED_ENTRY      , "An entry in the JAR has been modified after the JAR was signed.")
 XPC_MSG_DEF(NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY      , "An entry in the JAR has not been signed.")
 XPC_MSG_DEF(NS_ERROR_SIGNED_JAR_ENTRY_MISSING       , "An entry is missing from the JAR file.")
 XPC_MSG_DEF(NS_ERROR_SIGNED_JAR_WRONG_SIGNATURE     , "The JAR's signature is wrong.")
 XPC_MSG_DEF(NS_ERROR_SIGNED_JAR_ENTRY_TOO_LARGE     , "An entry in the JAR is too large.")
 XPC_MSG_DEF(NS_ERROR_SIGNED_JAR_ENTRY_INVALID       , "An entry in the JAR is invalid.")
 XPC_MSG_DEF(NS_ERROR_SIGNED_JAR_MANIFEST_INVALID    , "The JAR's manifest or signature file is invalid.")
+XPC_MSG_DEF(NS_ERROR_CMS_VERIFY_NOT_SIGNED          , "The PKCS#7 information is not signed.")
 
 /* Codes related to signed manifests */
 XPC_MSG_DEF(NS_ERROR_SIGNED_APP_MANIFEST_INVALID   , "The signed app manifest or signature file is invalid.")
 
 /* Codes for printing-related errors. */
 XPC_MSG_DEF(NS_ERROR_GFX_PRINTER_NO_PRINTER_AVAILABLE , "No printers available.")
 XPC_MSG_DEF(NS_ERROR_GFX_PRINTER_NAME_NOT_FOUND       , "The selected printer could not be found.")
 XPC_MSG_DEF(NS_ERROR_GFX_PRINTER_COULD_NOT_OPEN_FILE  , "Failed to open output file for print to file.")
--- a/security/apps/AppSignatureVerification.cpp
+++ b/security/apps/AppSignatureVerification.cpp
@@ -136,18 +136,18 @@ ReadStream(const nsCOMPtr<nsIInputStream
   }
 
   buf.data[buf.len - 1] = 0; // null-terminate
 
   return NS_OK;
 }
 
 // Finds exactly one (signature metadata) JAR entry that matches the given
-// search pattern, and then load it. Fails if there are no matches or if
-// there is more than one match. If bugDigest is not null then on success
+// search pattern, and then loads it. Fails if there are no matches or if
+// there is more than one match. If bufDigest is not null then on success
 // bufDigest will contain the digeset of the entry using the given digest
 // algorithm.
 nsresult
 FindAndLoadOneEntry(nsIZipReader* zip,
                     const nsACString& searchPattern,
                     /*out*/ nsACString& filename,
                     /*out*/ SECItem& buf,
                     /*optional, in*/ SECOidTag digestAlgorithm = SEC_OID_SHA1,
@@ -462,101 +462,98 @@ CheckManifestVersion(const char* & nextL
   if (!curLine.Equals(expectedHeader)) {
     return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
   }
   return NS_OK;
 }
 
 // Parses a signature file (SF) based on the JDK 8 JAR Specification.
 //
-// The SF file must contain a SHA1-Digest-Manifest (or, preferrably,
-// SHA256-Digest-Manifest) attribute in the main section. All other sections are
-// ignored. This means that this will NOT parse old-style signature files that
-// have separate digests per entry.
+// The SF file must contain a SHA*-Digest-Manifest attribute in the main
+// section (where the * is either 1 or 256, depending on the given digest
+// algorithm). All other sections are ignored. This means that this will NOT
+// parse old-style signature files that have separate digests per entry.
 // The JDK8 x-Digest-Manifest variant is better because:
 //
 //   (1) It allows us to follow the principle that we should minimize the
 //       processing of data that we do before we verify its signature. In
 //       particular, with the x-Digest-Manifest style, we can verify the digest
 //       of MANIFEST.MF before we parse it, which prevents malicious JARs
 //       exploiting our MANIFEST.MF parser.
 //   (2) It is more time-efficient and space-efficient to have one
 //       x-Digest-Manifest instead of multiple x-Digest values.
 //
 // filebuf must be null-terminated. On output, mfDigest will contain the
-// decoded value of SHA1-Digest-Manifest or SHA256-Digest-Manifest, if found,
-// as well as an identifier indicating which algorithm was found.
+// decoded value of the appropriate SHA*-DigestManifest, if found.
 nsresult
-ParseSF(const char* filebuf, /*out*/ DigestWithAlgorithm& mfDigest)
+ParseSF(const char* filebuf, SECOidTag digestAlgorithm,
+        /*out*/ nsAutoCString& mfDigest)
 {
+  const char* digestNameToFind = nullptr;
+  switch (digestAlgorithm) {
+    case SEC_OID_SHA256:
+      digestNameToFind = "sha256-digest-manifest";
+      break;
+    case SEC_OID_SHA1:
+      digestNameToFind = "sha1-digest-manifest";
+      break;
+    default:
+      MOZ_ASSERT_UNREACHABLE("bad argument to ParseSF");
+      return NS_ERROR_FAILURE;
+  }
+
   const char* nextLineStart = filebuf;
   nsresult rv = CheckManifestVersion(nextLineStart,
                                      NS_LITERAL_CSTRING(JAR_SF_HEADER));
   if (NS_FAILED(rv)) {
     return rv;
   }
 
-  nsAutoCString savedSHA1Digest;
-  // Search for SHA256-Digest-Manifest and SHA1-Digest-Manifest. Prefer the
-  // former.
   for (;;) {
     nsAutoCString curLine;
     rv = ReadLine(nextLineStart, curLine);
     if (NS_FAILED(rv)) {
       return rv;
     }
 
     if (curLine.Length() == 0) {
-      // End of main section (blank line or end-of-file). We didn't find
-      // SHA256-Digest-Manifest, but maybe we found SHA1-Digest-Manifest.
-      if (!savedSHA1Digest.IsEmpty()) {
-        mfDigest.mDigest.Assign(savedSHA1Digest);
-        mfDigest.mAlgorithm = SEC_OID_SHA1;
-        return NS_OK;
-      }
+      // End of main section (blank line or end-of-file). We didn't find the
+      // SHA*-Digest-Manifest we were looking for.
       return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
     }
 
     nsAutoCString attrName;
     nsAutoCString attrValue;
     rv = ParseAttribute(curLine, attrName, attrValue);
     if (NS_FAILED(rv)) {
       return rv;
     }
 
-    if (attrName.LowerCaseEqualsLiteral("sha256-digest-manifest")) {
-      rv = Base64Decode(attrValue, mfDigest.mDigest);
+    if (attrName.EqualsIgnoreCase(digestNameToFind)) {
+      rv = Base64Decode(attrValue, mfDigest);
       if (NS_FAILED(rv)) {
         return rv;
       }
-      mfDigest.mAlgorithm = SEC_OID_SHA256;
 
       // There could be multiple SHA*-Digest-Manifest attributes, which
       // would be an error, but it's better to just skip any erroneous
       // duplicate entries rather than trying to detect them, because:
       //
       //   (1) It's simpler, and simpler generally means more secure
       //   (2) An attacker can't make us accept a JAR we would otherwise
       //       reject just by adding additional SHA*-Digest-Manifest
       //       attributes.
       return NS_OK;
     }
 
-    if (attrName.LowerCaseEqualsLiteral("sha1-digest-manifest") &&
-        savedSHA1Digest.IsEmpty()) {
-      rv = Base64Decode(attrValue, savedSHA1Digest);
-      if (NS_FAILED(rv)) {
-        return rv;
-      }
-    }
-
     // ignore unrecognized attributes
   }
 
-  return NS_OK;
+  MOZ_ASSERT_UNREACHABLE("somehow exited loop in ParseSF without returning");
+  return NS_ERROR_FAILURE;
 }
 
 // Parses MANIFEST.MF. The filenames of all entries will be returned in
 // mfItems. buf must be a pre-allocated scratch buffer that is used for doing
 // I/O. Each file's contents are verified against the entry in the manifest with
 // the digest algorithm that matches the given one. This algorithm comes from
 // the signature file. If the signature file has a SHA-256 digest, then SHA-256
 // entries must be present in the manifest file. If the signature file only has
@@ -753,26 +750,66 @@ VerifyCertificate(CERTCertificate* signe
   }
   if (result != Success) {
     return mozilla::psm::GetXPCOMFromNSSError(MapResultToPRErrorCode(result));
   }
 
   return NS_OK;
 }
 
+// Given a SECOidTag representing a digest algorithm (either SEC_OID_SHA1 or
+// SEC_OID_SHA256), returns the first signerInfo in the given signedData that
+// purports to have been created using that digest algorithm, or nullptr if
+// there is none.
+// The returned signerInfo is owned by signedData, so the caller must ensure
+// that the lifetime of the signerInfo is contained by the lifetime of the
+// signedData.
+NSSCMSSignerInfo*
+GetSignerInfoForDigestAlgorithm(NSSCMSSignedData* signedData,
+                                SECOidTag digestAlgorithm)
+{
+  MOZ_ASSERT(digestAlgorithm == SEC_OID_SHA1 ||
+             digestAlgorithm == SEC_OID_SHA256);
+  if (digestAlgorithm != SEC_OID_SHA1 && digestAlgorithm != SEC_OID_SHA256) {
+    return nullptr;
+  }
+
+  int numSigners = NSS_CMSSignedData_SignerInfoCount(signedData);
+  if (numSigners < 1) {
+    return nullptr;
+  }
+  for (int i = 0; i < numSigners; i++) {
+    NSSCMSSignerInfo* signerInfo =
+      NSS_CMSSignedData_GetSignerInfo(signedData, i);
+    // NSS_CMSSignerInfo_GetDigestAlgTag isn't exported from NSS.
+    SECOidData* digestAlgOID = SECOID_FindOID(&signerInfo->digestAlg.algorithm);
+    if (!digestAlgOID) {
+      continue;
+    }
+    if (digestAlgorithm == digestAlgOID->offset) {
+      return signerInfo;
+    }
+  }
+  return nullptr;
+}
+
 nsresult
 VerifySignature(AppTrustedRoot trustedRoot, const SECItem& buffer,
-                const SECItem& detachedDigest,
+                const SECItem& detachedSHA1Digest,
+                const SECItem& detachedSHA256Digest,
+                /*out*/ SECOidTag& digestAlgorithm,
                 /*out*/ UniqueCERTCertList& builtChain)
 {
   // Currently, this function is only called within the CalculateResult() method
   // of CryptoTasks. As such, NSS should not be shut down at this point and the
   // CryptoTask implementation should already hold a nsNSSShutDownPreventionLock.
-  if (NS_WARN_IF(!buffer.data && buffer.len > 0) ||
-      NS_WARN_IF(!detachedDigest.data && detachedDigest.len > 0)) {
+
+  if (NS_WARN_IF(!buffer.data || buffer.len == 0 || !detachedSHA1Digest.data ||
+                 detachedSHA1Digest.len == 0 || !detachedSHA256Digest.data ||
+                 detachedSHA256Digest.len == 0)) {
     return NS_ERROR_INVALID_ARG;
   }
 
   UniqueNSSCMSMessage
     cmsMsg(NSS_CMSMessage_CreateFromDER(const_cast<SECItem*>(&buffer), nullptr,
                                         nullptr, nullptr, nullptr, nullptr,
                                         nullptr));
   if (!cmsMsg) {
@@ -796,22 +833,16 @@ VerifySignature(AppTrustedRoot trustedRo
 
   // signedData is non-owning
   NSSCMSSignedData* signedData =
     static_cast<NSSCMSSignedData*>(NSS_CMSContentInfo_GetContent(cinfo));
   if (!signedData) {
     return NS_ERROR_CMS_VERIFY_NO_CONTENT_INFO;
   }
 
-  // Set digest value.
-  if (NSS_CMSSignedData_SetDigestValue(signedData, SEC_OID_SHA1,
-                                       const_cast<SECItem*>(&detachedDigest))) {
-    return NS_ERROR_CMS_VERIFY_BAD_DIGEST;
-  }
-
   // Parse the certificates into CERTCertificate objects held in memory so
   // verifyCertificate will be able to find them during path building.
   UniqueCERTCertList certs(CERT_NewCertList());
   if (!certs) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
   if (signedData->rawCerts) {
     for (size_t i = 0; signedData->rawCerts[i]; ++i) {
@@ -827,47 +858,53 @@ VerifySignature(AppTrustedRoot trustedRo
       if (CERT_AddCertToListTail(certs.get(), cert.get()) != SECSuccess) {
         return NS_ERROR_OUT_OF_MEMORY;
       }
 
       Unused << cert.release(); // Ownership transferred to the cert list.
     }
   }
 
-  // Get the end-entity certificate.
-  int numSigners = NSS_CMSSignedData_SignerInfoCount(signedData);
-  if (NS_WARN_IF(numSigners != 1)) {
-    return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING;
+  NSSCMSSignerInfo* signerInfo =
+    GetSignerInfoForDigestAlgorithm(signedData, SEC_OID_SHA256);
+  const SECItem* detachedDigest = &detachedSHA256Digest;
+  digestAlgorithm = SEC_OID_SHA256;
+  if (!signerInfo) {
+    signerInfo = GetSignerInfoForDigestAlgorithm(signedData, SEC_OID_SHA1);
+    if (!signerInfo) {
+      return NS_ERROR_CMS_VERIFY_NOT_SIGNED;
+    }
+    detachedDigest = &detachedSHA1Digest;
+    digestAlgorithm = SEC_OID_SHA1;
   }
-  // signer is non-owning.
-  NSSCMSSignerInfo* signer = NSS_CMSSignedData_GetSignerInfo(signedData, 0);
-  if (NS_WARN_IF(!signer)) {
-    return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING;
-  }
+
+  // Get the end-entity certificate.
   CERTCertificate* signerCert =
-    NSS_CMSSignerInfo_GetSigningCertificate(signer, CERT_GetDefaultCertDB());
+    NSS_CMSSignerInfo_GetSigningCertificate(signerInfo,
+                                            CERT_GetDefaultCertDB());
   if (!signerCert) {
     return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING;
   }
 
   nsresult rv = VerifyCertificate(signerCert, trustedRoot, builtChain);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
+  if (NS_FAILED(rv)) {
     return rv;
   }
 
-  // See NSS_CMSContentInfo_GetContentTypeOID, which isn't exported from NSS.
-  SECOidData* contentTypeOidData =
-    SECOID_FindOID(&signedData->contentInfo.contentType);
-  if (!contentTypeOidData) {
+  // Ensure that the PKCS#7 data OID is present as the PKCS#9 contentType.
+  const char* pkcs7DataOidString = "1.2.840.113549.1.7.1";
+  ScopedAutoSECItem pkcs7DataOid;
+  if (SEC_StringToOID(nullptr, &pkcs7DataOid, pkcs7DataOidString, 0)
+        != SECSuccess) {
     return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING;
   }
 
-  return MapSECStatus(NSS_CMSSignerInfo_Verify(signer,
-                         const_cast<SECItem*>(&detachedDigest),
-                         &contentTypeOidData->oid));
+  return MapSECStatus(
+    NSS_CMSSignerInfo_Verify(signerInfo, const_cast<SECItem*>(detachedDigest),
+                             &pkcs7DataOid));
 }
 
 nsresult
 OpenSignedAppFile(AppTrustedRoot aTrustedRoot, nsIFile* aJarFile,
                   /*out, optional */ nsIZipReader** aZipReader,
                   /*out, optional */ nsIX509Cert** aSignerCert)
 {
   NS_ENSURE_ARG_POINTER(aJarFile);
@@ -896,64 +933,79 @@ OpenSignedAppFile(AppTrustedRoot aTruste
                            sigFilename, sigBuffer);
   if (NS_FAILED(rv)) {
     return NS_ERROR_SIGNED_JAR_NOT_SIGNED;
   }
 
   // Signature (SF) file
   nsAutoCString sfFilename;
   ScopedAutoSECItem sfBuffer;
-  Digest sfCalculatedDigest;
   rv = FindAndLoadOneEntry(zip, NS_LITERAL_CSTRING(JAR_SF_SEARCH_STRING),
-                           sfFilename, sfBuffer, SEC_OID_SHA1,
-                           &sfCalculatedDigest);
+                           sfFilename, sfBuffer);
   if (NS_FAILED(rv)) {
     return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
   }
 
-  sigBuffer.type = siBuffer;
-  UniqueCERTCertList builtChain;
-  rv = VerifySignature(aTrustedRoot, sigBuffer, sfCalculatedDigest.get(),
-                       builtChain);
+  // Calculate both the SHA-1 and SHA-256 hashes of the signature file - we
+  // don't know what algorithm the PKCS#7 signature used.
+  Digest sfCalculatedSHA1Digest;
+  rv = sfCalculatedSHA1Digest.DigestBuf(SEC_OID_SHA1, sfBuffer.data,
+                                        sfBuffer.len - 1);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  Digest sfCalculatedSHA256Digest;
+  rv = sfCalculatedSHA256Digest.DigestBuf(SEC_OID_SHA256, sfBuffer.data,
+                                          sfBuffer.len - 1);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
-  DigestWithAlgorithm mfDigest;
-  rv = ParseSF(BitwiseCast<char*, unsigned char*>(sfBuffer.data), mfDigest);
+  sigBuffer.type = siBuffer;
+  UniqueCERTCertList builtChain;
+  SECOidTag digestToUse;
+  rv = VerifySignature(aTrustedRoot, sigBuffer, sfCalculatedSHA1Digest.get(),
+                       sfCalculatedSHA256Digest.get(), digestToUse, builtChain);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  nsAutoCString mfDigest;
+  rv = ParseSF(BitwiseCast<char*, unsigned char*>(sfBuffer.data), digestToUse,
+               mfDigest);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   // Manifest (MF) file
   nsAutoCString mfFilename;
   ScopedAutoSECItem manifestBuffer;
   Digest mfCalculatedDigest;
   rv = FindAndLoadOneEntry(zip, NS_LITERAL_CSTRING(JAR_MF_SEARCH_STRING),
-                           mfFilename, manifestBuffer, mfDigest.mAlgorithm,
+                           mfFilename, manifestBuffer, digestToUse,
                            &mfCalculatedDigest);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   nsDependentCSubstring calculatedDigest(
     DigestToDependentString(mfCalculatedDigest));
-  if (!mfDigest.mDigest.Equals(calculatedDigest)) {
+  if (!mfDigest.Equals(calculatedDigest)) {
     return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
   }
 
   // Allocate the I/O buffer only once per JAR, instead of once per entry, in
   // order to minimize malloc/free calls and in order to avoid fragmenting
   // memory.
   ScopedAutoSECItem buf(128 * 1024);
 
   nsTHashtable<nsCStringHashKey> items;
 
   rv = ParseMF(BitwiseCast<char*, unsigned char*>(manifestBuffer.data), zip,
-               mfDigest.mAlgorithm, items, buf);
+               digestToUse, items, buf);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   // Verify every entry in the file.
   nsCOMPtr<nsIUTF8StringEnumerator> entries;
   rv = zip->FindEntries(EmptyCString(), getter_AddRefs(entries));
   if (NS_SUCCEEDED(rv) && !entries) {
@@ -1471,66 +1523,81 @@ VerifySignedDirectory(AppTrustedRoot aTr
 
   // Load the signature (SF) file and verify the signature.
   // The .sf and .rsa files must have the same name apart from the extension.
 
   nsAutoString sfFilename(Substring(sigFilename, 0, sigFilename.Length() - 3)
                           + NS_LITERAL_STRING("sf"));
 
   ScopedAutoSECItem sfBuffer;
-  Digest sfCalculatedDigest;
-  rv = LoadOneMetafile(metaDir, sfFilename, sfBuffer, SEC_OID_SHA1,
-                       &sfCalculatedDigest);
+  rv = LoadOneMetafile(metaDir, sfFilename, sfBuffer);
   if (NS_FAILED(rv)) {
     return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
   }
 
+  // Calculate both the SHA-1 and SHA-256 hashes of the signature file - we
+  // don't know what algorithm the PKCS#7 signature used.
+  Digest sfCalculatedSHA1Digest;
+  rv = sfCalculatedSHA1Digest.DigestBuf(SEC_OID_SHA1, sfBuffer.data,
+                                        sfBuffer.len - 1);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  Digest sfCalculatedSHA256Digest;
+  rv = sfCalculatedSHA256Digest.DigestBuf(SEC_OID_SHA256, sfBuffer.data,
+                                          sfBuffer.len - 1);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
   sigBuffer.type = siBuffer;
   UniqueCERTCertList builtChain;
-  rv = VerifySignature(aTrustedRoot, sigBuffer, sfCalculatedDigest.get(),
-                       builtChain);
+  SECOidTag digestToUse;
+  rv = VerifySignature(aTrustedRoot, sigBuffer, sfCalculatedSHA1Digest.get(),
+                       sfCalculatedSHA256Digest.get(), digestToUse, builtChain);
   if (NS_FAILED(rv)) {
     return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
   }
 
   // Get the expected manifest hash from the signed .sf file
 
-  DigestWithAlgorithm mfDigest;
-  rv = ParseSF(BitwiseCast<char*, unsigned char*>(sfBuffer.data), mfDigest);
+  nsAutoCString mfDigest;
+  rv = ParseSF(BitwiseCast<char*, unsigned char*>(sfBuffer.data), digestToUse,
+               mfDigest);
   if (NS_FAILED(rv)) {
     return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
   }
 
   // Load manifest (MF) file and verify signature
 
   nsAutoString mfFilename(NS_LITERAL_STRING("manifest.mf"));
   ScopedAutoSECItem manifestBuffer;
   Digest mfCalculatedDigest;
-  rv = LoadOneMetafile(metaDir, mfFilename, manifestBuffer, mfDigest.mAlgorithm,
+  rv = LoadOneMetafile(metaDir, mfFilename, manifestBuffer, digestToUse,
                        &mfCalculatedDigest);
   if (NS_FAILED(rv)) {
     return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
   }
 
   nsDependentCSubstring calculatedDigest(
     DigestToDependentString(mfCalculatedDigest));
-  if (!mfDigest.mDigest.Equals(calculatedDigest)) {
+  if (!mfDigest.Equals(calculatedDigest)) {
     return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
   }
 
   // Parse manifest and verify signed hash of all listed files
 
   // Allocate the I/O buffer only once per JAR, instead of once per entry, in
   // order to minimize malloc/free calls and in order to avoid fragmenting
   // memory.
   ScopedAutoSECItem buf(128 * 1024);
 
   nsTHashtable<nsStringHashKey> items;
   rv = ParseMFUnpacked(BitwiseCast<char*, unsigned char*>(manifestBuffer.data),
-                       aDirectory, mfDigest.mAlgorithm, items, buf);
+                       aDirectory, digestToUse, items, buf);
   if (NS_FAILED(rv)){
     return rv;
   }
 
   // We've checked that everything listed in the manifest exists and is signed
   // correctly. Now check on disk for extra (unsigned) files.
   // Deletes found entries from items as it goes.
   rv = CheckDirForUnsignedFiles(aDirectory, EmptyString(), items,
--- a/security/manager/ssl/tests/unit/pycms.py
+++ b/security/manager/ssl/tests/unit/pycms.py
@@ -5,24 +5,30 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 """
 Reads a specification from stdin and outputs a PKCS7 (CMS) message with
 the desired properties.
 
 The specification format is as follows:
 
-hash:<hex string>
+sha1:<hex string>
+sha256:<hex string>
 signer:
 <pycert specification>
 
-hash is the value that will be put in the messageDigest attribute in
-each SignerInfo of the signerInfos field of the SignedData.
+Eith or both of sha1 and sha256 may be specified. The value of
+each hash directive is what will be put in the messageDigest
+attribute of the SignerInfo that corresponds to the signature
+algorithm defined by the hash algorithm and key type of the
+default key. Together, these comprise the signerInfos field of
+the SignedData. If neither hash is specified, the signerInfos
+will be an empty SET (i.e. there will be no actual signature
+information).
 The certificate specification must come last.
-Currently only SHA-1 is supported.
 """
 
 from pyasn1.codec.der import decoder
 from pyasn1.codec.der import encoder
 from pyasn1.type import tag, univ
 from pyasn1_modules import rfc2315, rfc2459
 import StringIO
 import base64
@@ -47,26 +53,29 @@ class UnknownDirectiveError(Error):
         return 'Unknown directive %s' % repr(self.directive)
 
 
 class CMS(object):
     """Utility class for reading a CMS specification and
     generating a CMS message"""
 
     def __init__(self, paramStream):
-        self.hash = ''
+        self.sha1 = ''
+        self.sha256 = ''
         signerSpecification = StringIO.StringIO()
         readingSignerSpecification = False
         for line in paramStream.readlines():
             if readingSignerSpecification:
                 print >>signerSpecification, line.strip()
             elif line.strip() == 'signer:':
                 readingSignerSpecification = True
-            elif line.startswith('hash:'):
-                self.hash = line.strip()[len('hash:'):]
+            elif line.startswith('sha1:'):
+                self.sha1 = line.strip()[len('sha1:'):]
+            elif line.startswith('sha256:'):
+                self.sha256 = line.strip()[len('sha256:'):]
             else:
                 raise UnknownDirectiveError(line.strip())
         signerSpecification.seek(0)
         self.signer = pycert.Certificate(signerSpecification)
         self.signingKey = pykey.keyFromSpecification('default')
 
     def buildAuthenticatedAttributes(self, value, implicitTag=None):
         """Utility function to build a pyasn1 AuthenticatedAttributes
@@ -92,21 +101,25 @@ class CMS(object):
         hashAttribute['values'][0] = univ.OctetString(hexValue=value)
         authenticatedAttributes[1] = hashAttribute
         return authenticatedAttributes
 
     def pykeyHashToDigestAlgorithm(self, pykeyHash):
         """Given a pykey hash algorithm identifier, builds an
         AlgorithmIdentifier for use with pyasn1."""
         if pykeyHash == pykey.HASH_SHA1:
-            algorithmIdentifier = rfc2459.AlgorithmIdentifier()
-            algorithmIdentifier['algorithm'] = univ.ObjectIdentifier('1.3.14.3.2.26')
-            algorithmIdentifier['parameters'] = univ.Null()
-            return algorithmIdentifier
-        raise pykey.UnknownHashAlgorithmError(pykeyHash)
+            oidString = '1.3.14.3.2.26'
+        elif pykeyHash == pykey.HASH_SHA256:
+            oidString = '2.16.840.1.101.3.4.2.1'
+        else:
+            raise pykey.UnknownHashAlgorithmError(pykeyHash)
+        algorithmIdentifier = rfc2459.AlgorithmIdentifier()
+        algorithmIdentifier['algorithm'] = univ.ObjectIdentifier(oidString)
+        algorithmIdentifier['parameters'] = univ.Null()
+        return algorithmIdentifier
 
     def buildSignerInfo(self, certificate, pykeyHash, digestValue):
         """Given a pyasn1 certificate, a pykey hash identifier
         and a hash value, creates a SignerInfo with the
         appropriate values."""
         signerInfo = rfc2315.SignerInfo()
         signerInfo['version'] = 1
         issuerAndSerialNumber = rfc2315.IssuerAndSerialNumber()
@@ -152,17 +165,22 @@ class CMS(object):
         certificate = decoder.decode(self.signer.toDER(),
             asn1Spec=rfc2459.Certificate())[0]
         extendedCertificateOrCertificate['certificate'] = certificate
         certificates[0] = extendedCertificateOrCertificate
         signedData['certificates'] = certificates
 
         signerInfos = rfc2315.SignerInfos()
 
-        signerInfos[0] = self.buildSignerInfo(certificate, pykey.HASH_SHA1, self.hash)
+        if len(self.sha1) > 0:
+            signerInfos[len(signerInfos)] = self.buildSignerInfo(certificate,
+                pykey.HASH_SHA1, self.sha1)
+        if len(self.sha256) > 0:
+            signerInfos[len(signerInfos)] = self.buildSignerInfo(certificate,
+                pykey.HASH_SHA256, self.sha256)
         signedData['signerInfos'] = signerInfos
 
         encoded = encoder.encode(signedData)
         anyTag = univ.Any(encoded).subtype(
             explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))
 
         contentInfo['content'] = anyTag
         return encoder.encode(contentInfo)
--- a/security/manager/ssl/tests/unit/sign_app.py
+++ b/security/manager/ssl/tests/unit/sign_app.py
@@ -28,24 +28,25 @@ def walkDirectory(directory):
     for path, dirs, files in os.walk(directory):
         for f in files:
             fullPath = os.path.join(path, f)
             internalPath = re.sub(r'^/', '', fullPath.replace(directory, ''))
             paths.append((fullPath, internalPath))
     return paths
 
 def signZip(appDirectory, outputFile, issuerName, manifestHashes,
-            signatureHashes, doSign):
+            signatureHashes, pkcs7Hashes, doSign):
     """Given a directory containing the files to package up,
     an output filename to write to, the name of the issuer of
     the signing certificate, a list of hash algorithms to use in
     the manifest file, a similar list for the signature file,
-    and whether or not to actually sign the resulting package,
-    packages up the files in the directory and creates the
-    output as appropriate."""
+    a similar list for the pkcs#7 signature, and whether or not
+    to actually sign the resulting package, packages up the
+    files in the directory and creates the output as
+    appropriate."""
     mfEntries = []
 
     with zipfile.ZipFile(outputFile, 'w') as outZip:
         for (fullPath, internalPath) in walkDirectory(appDirectory):
             with open(fullPath) as inputFile:
                 contents = inputFile.read()
             outZip.writestr(internalPath, contents)
 
@@ -61,17 +62,22 @@ def signZip(appDirectory, outputFile, is
             return
 
         mfContents = 'Manifest-Version: 1.0\n\n' + '\n'.join(mfEntries)
         sfContents = 'Signature-Version: 1.0\n'
         for (hashFunc, name) in signatureHashes:
             base64hash = b64encode(hashFunc(mfContents).digest())
             sfContents += '%s-Digest-Manifest: %s\n' % (name, base64hash)
 
-        cmsSpecification = 'hash:%s\nsigner:\n' % sha1(sfContents).hexdigest() + \
+        cmsSpecification = ''
+        for name in pkcs7Hashes:
+            hashFunc, _ = hashNameToFunctionAndIdentifier(name)
+            cmsSpecification += '%s:%s\n' % (name,
+                                             hashFunc(sfContents).hexdigest())
+        cmsSpecification += 'signer:\n' + \
             'issuer:%s\n' % issuerName + \
             'subject:xpcshell signed app test signer\n' + \
             'extension:keyUsage:digitalSignature'
         cmsSpecificationStream = StringIO.StringIO()
         print >>cmsSpecificationStream, cmsSpecification
         cmsSpecificationStream.seek(0)
         cms = pycms.CMS(cmsSpecificationStream)
         p7 = cms.toDER()
@@ -113,17 +119,25 @@ def main(outputFile, appPath, *args):
     parser.add_argument('-i', '--issuer', action='store', help='Issuer name',
                         default='xpcshell signed apps test root')
     parser.add_argument('-m', '--manifest-hash', action='append',
                         help='Hash algorithms to use in manifest',
                         default=[])
     parser.add_argument('-s', '--signature-hash', action='append',
                         help='Hash algorithms to use in signature file',
                         default=[])
+    group = parser.add_mutually_exclusive_group()
+    group.add_argument('-p', '--pkcs7-hash', action='append',
+                       help='Hash algorithms to use in PKCS#7 signature',
+                       default=[])
+    group.add_argument('-e', '--empty-signerInfos', action='store_true',
+                       help='Emit pkcs#7 SignedData with empty signerInfos')
     parsed = parser.parse_args(args)
     if len(parsed.manifest_hash) == 0:
         parsed.manifest_hash.append('sha256')
     if len(parsed.signature_hash) == 0:
         parsed.signature_hash.append('sha256')
+    if len(parsed.pkcs7_hash) == 0 and not parsed.empty_signerInfos:
+        parsed.pkcs7_hash.append('sha256')
     signZip(appPath, outputFile, parsed.issuer,
             map(hashNameToFunctionAndIdentifier, parsed.manifest_hash),
             map(hashNameToFunctionAndIdentifier, parsed.signature_hash),
-            not parsed.no_sign)
+            parsed.pkcs7_hash, not parsed.no_sign)
--- a/security/manager/ssl/tests/unit/test_signed_apps.js
+++ b/security/manager/ssl/tests/unit/test_signed_apps.js
@@ -138,24 +138,25 @@ add_test(function () {
   certdb.openSignedAppFileAsync(
     Ci.nsIX509CertDB.AppXPCShellRoot, original_app_path("signed_app"),
     check_open_result("valid", Cr.NS_OK));
 });
 
 add_test(function () {
   certdb.openSignedAppFileAsync(
     Ci.nsIX509CertDB.AppXPCShellRoot, original_app_path("signed_app_sha256"),
-    check_open_result("valid with sha256 hashes in manifest", Cr.NS_OK));
+    check_open_result("valid with sha256 everywhere", Cr.NS_OK));
 });
 
 add_test(function () {
   certdb.openSignedAppFileAsync(
     Ci.nsIX509CertDB.AppXPCShellRoot,
     original_app_path("signed_app_sha1_and_sha256"),
-    check_open_result("valid with sha1 and sha256 hashes in manifest", Cr.NS_OK));
+    check_open_result("valid with sha1 and sha256 hashes everywhere",
+                      Cr.NS_OK));
 });
 
 add_test(function () {
   certdb.openSignedAppFileAsync(
     Ci.nsIX509CertDB.AppXPCShellRoot,
     original_app_path("signed_app_sha256_manifest"),
     check_open_result("sha256 hashes in manifest, but only a sha1 hash in the signature file",
                       Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID));
@@ -177,33 +178,41 @@ add_test(function () {
                       Cr.NS_OK));
 });
 
 add_test(function () {
   certdb.openSignedAppFileAsync(
     Ci.nsIX509CertDB.AppXPCShellRoot,
     original_app_path("sha1_and_sha256_manifest_sha256_signature_file"),
     check_open_result("sha1 and sha256 hashes in the manifest, but only sha256 hash in the signature file",
-                      Cr.NS_OK));
+                      Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID));
 });
 
 add_test(function () {
   certdb.openSignedAppFileAsync(
     Ci.nsIX509CertDB.AppXPCShellRoot,
     original_app_path("sha1_manifest_sha1_and_sha256_signature_file"),
     check_open_result("only sha1 in the manifest, sha1 and sha256 hashes in the signature file",
-                      Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID));
+                      Cr.NS_OK));
 });
 
 add_test(function () {
   certdb.openSignedAppFileAsync(
     Ci.nsIX509CertDB.AppXPCShellRoot,
     original_app_path("sha256_manifest_sha1_and_sha256_signature_file"),
     check_open_result("only sha256 in the manifest, sha1 and sha256 hashes in the signature file",
-                      Cr.NS_OK));
+                      Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID));
+});
+
+add_test(function () {
+  certdb.openSignedAppFileAsync(
+    Ci.nsIX509CertDB.AppXPCShellRoot,
+    original_app_path("empty_signerInfos"),
+    check_open_result("the signerInfos in the PKCS#7 signature is empty",
+                      Cr.NS_ERROR_CMS_VERIFY_NOT_SIGNED));
 });
 
 add_test(function () {
   certdb.openSignedAppFileAsync(
     Ci.nsIX509CertDB.AppXPCShellRoot, original_app_path("unsigned_app"),
     check_open_result("unsigned", Cr.NS_ERROR_SIGNED_JAR_NOT_SIGNED));
 });
 
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..94261ff0c5ee298142dd7ed6efcc4cab210f6395
GIT binary patch
literal 2458
zc$^FHW@Zs#0D-A~(cV%q8k_upY!K!J;@rf%%(T?v61}YA{Jd%|1qCITNPJmpQE_H|
zo|2V<kq$^AFEKY2$WaObN-DTkl%(c?rNMFtwMqth26~1{T(w*QC>DS7Fx1fovO$;)
zh=W`mU3^_bGBS%5fT$!RRiP*~F(o%uAuYcM%q;;~lM1s&56!sQO_uCpObiSl%m)-p
zNi0d!&&*9sPt_~POYaQubLZuf;sSZn)59eQNKXS{4v@_@jD?RF7#Qz*x;TbZ%z1lm
zBPWA_z>y8N>tCK1Vr(v47<c>Kr2iir=8U2ti+bb|3Y4n$$q4TOr58_EKbLh*2~DUe
zXi5LQ33|*73?K|jLBPc5sO#zHrthd16zpiw#H_`}snzDu_MMlJooPW6voughoQaXq
zklTQhja8eEnMsP3!GIf~h;cy^(-(s#rVj>9jJFmrGchtTiHOdrT=tneXzjbh57<7=
z@Ch!|{r(k64<omMrlGolDjRbs3p0;gML}|LMruxuLUCq#UTTU$VnIPMBp(#z=a(2L
zN$?vP7@8Uy8d(^CK@?cP!obMD6e?h#WvF4G1~E<^i*XRs!2BYhGnEW@5pH2*WngY%
z<Yxeib1^kBGBWJy2wdUPCw9&8Z{D^JHZuWR8{wO;o|t-VdGj?RJV&Ma;BnOz4{pfs
z-8hGBcAiw*r|d`Tqe`>0QVKb$mPRdiZT~KFRb>uS-C5^9a+wz_6f`HVRygeW<n~UV
zL)kfeZ$+E<Kh~|yl-hWi$*Nn+@!j%*2mHLff77_NH>gZnQoFMwv2w!3@Cmk;tdBm}
z;UT}m{Bno$dv&oJq1)4<R@E_0(6_1<Xkb%0Ba<_I?X<+`?T@Aj9Vj(i%ro)smQ&yV
ztd&*0%=vz&IN$P&hnuVQmGUzmZ|RzvDEm(Bn(Exu6FL0MPRppU=<FBk)_?l3f2O$M
zj?Mo(m46il9x=Siyylh!m+9MHCT2zk#>M;wyucV^4wdC+VPR%sZ$ODYW>7epSzEbF
zFV<q1$zAb<@6`Rw{DmDk_O5*f?XjZyD%}=O%b!e=Q(5`gT~7D3WPrk*mVzas7w3Kb
zf3sSgtKDewu}3y}_dk7<Ogiy=OV+_Og}UH*j906c-aq|yv5(8%nbZCpnU$#h<JPA?
zZ_AHlxNh4mZ+6l0<I#(0&paoa9G`e`>zVjOJ57<q{ahTIRK0FZJJ`VcxViZMo_y~$
z?na(kf@Nl}1P=3El5l#f_G^2N!}Vt&ypt=`mWxz|&Era(9{zszD{0Hqr@t-P`7-|d
z4}tkzWs*xa-dgZbK6~fnB~ncnrmlQ7^ZM8GwU<|Z4ZgIaq<Y55VAuBGgU&P4w+OJE
z<FSkIGGH`hK+RcU8k|lsKz0nYjzP*<!EV98w3k>?T9m3A2B|Zw6b$tYxPm<#jZDpS
zT{6>w2~^h?S_fGv_*)oxr$v^zxE1GQXE{5U_+=Lv<eT_L7PzHHRAprp86}lxR#sS8
zgytpNqE!*ExMwT{#vlU-iy%A0*U`_@%{4eg&({rR5vpUj{D4)Rl>$oLi0VQs1*eca
z=YkMJQxiWUv!JM4i=>Pc7Zc;~h!lg8EYqZPv-~t)LrXs&PXm8fTbO~6IuG5Xq)3At
z%ZfsGuSn;D!pKxF18=iJ6PNJNz`|04Tt6o_UlR|L)ZC1$Os{mfNvPEwx{0m<S=oM3
z<&}w+5!wYsr9L?o?%HmS&OYVt&L$QSp(eqW`BDCk203A2fuKNRWD;S<-9mtY21W+-
z_5!+A^g14)wGv+Aqw7Jh$Ps!@U{veqdeMt;gkCOY^a34SFM1+H=uKfmOS0%%(GwOz
Z>uWab$t=K|6&xOHK$rs5rN9B=0RRLSa9#ia
--- a/security/manager/ssl/tests/unit/test_signed_apps/moz.build
+++ b/security/manager/ssl/tests/unit/test_signed_apps/moz.build
@@ -20,23 +20,24 @@ def SignedAppFile(name, flags):
     # TEST_HARNESS_FILES.xpcshell.security.manager.ssl.tests.unit.test_signed_apps.
     files = TEST_HARNESS_FILES.xpcshell
     for part in RELATIVEDIR.split('/'):
         files = files[part]
     files += ['!%s' % name]
 
 # Temporarily disabled. See bug 1256495.
 #signed_app_files = (
-#    ['signed_app.zip', '-m', 'sha1', '-s', 'sha1'],
-#    ['unknown_issuer_app.zip', '-i', 'unknown issuer', '-m', 'sha1', '-s', 'sha1'],
+#    ['signed_app.zip', '-m', 'sha1', '-s', 'sha1', '-p', 'sha1'],
+#    ['unknown_issuer_app.zip', '-i', 'unknown issuer', '-m', 'sha1', '-s', 'sha1', '-p', 'sha1'],
 #    ['unsigned_app.zip', '-n'],
 #    ['signed_app_sha256.zip'],
-#    ['signed_app_sha1_and_sha256.zip', '-m', 'sha256', '-m', 'sha1', '-s', 'sha256', '-s', 'sha1'],
-#    ['signed_app_sha256_manifest.zip', '-s', 'sha1'],
-#    ['signed_app_sha256_signature_file.zip', '-m', 'sha1'],
-#    ['sha1_and_sha256_manifest_sha1_signature_file.zip', '-m', 'sha256', '-m', 'sha1', '-s', 'sha1'],
-#    ['sha1_and_sha256_manifest_sha256_signature_file.zip', '-m', 'sha256', '-m', 'sha1'],
-#    ['sha1_manifest_sha1_and_sha256_signature_file.zip', '-m', 'sha1', '-s', 'sha1', '-s', 'sha256'],
-#    ['sha256_manifest_sha1_and_sha256_signature_file.zip', '-s', 'sha1', '-s', 'sha256'],
+#    ['signed_app_sha1_and_sha256.zip', '-m', 'sha256', '-m', 'sha1', '-s', 'sha256', '-s', 'sha1', '-p', 'sha1', '-p', 'sha256'],
+#    ['signed_app_sha256_manifest.zip', '-s', 'sha1', '-p', 'sha1'],
+#    ['signed_app_sha256_signature_file.zip', '-m', 'sha1', '-p', 'sha1'],
+#    ['sha1_and_sha256_manifest_sha1_signature_file.zip', '-m', 'sha256', '-m', 'sha1', '-s', 'sha1', '-p', 'sha1'],
+#    ['sha1_and_sha256_manifest_sha256_signature_file.zip', '-m', 'sha256', '-m', 'sha1', '-p', 'sha1'],
+#    ['sha1_manifest_sha1_and_sha256_signature_file.zip', '-m', 'sha1', '-s', 'sha1', '-s', 'sha256', '-p', 'sha1'],
+#    ['sha256_manifest_sha1_and_sha256_signature_file.zip', '-s', 'sha1', '-s', 'sha256', '-p', 'sha1'],
+#    ['empty_signerInfos.zip', '-e'],
 #)
 #
 #for signed_app_file_params in signed_app_files:
 #    SignedAppFile(signed_app_file_params[0], signed_app_file_params[1:])
index 5770318d0dd764eb641fa32fbc3f32b6bdeea107..bd861664cc99a7672935f465cbcd079d67d431cb
GIT binary patch
literal 2993
zc$^FHW@Zs#0D&of(cV%q8k_upY!K!J;@rf%%(T?v61}YA{Jd%|1qCITNPJmpQE_H|
zo|2V<kq$^AFEKY2$WaObN-DTkl%(c?rNMFtwMqth26~1{T(w*QC>DS7Fx1fovO$;)
zh=W`mU3^_bGBS%5fT$!RRiP*~F(o%uAuYcM%q;;~lM1s&56!sQO_uCpObiSl%m)-p
zNi0d!&&*9sPt_~POYaQubLZuf;sSZn)59eQNKXS{4v@_@jD?RF7#Qz*x;TbZ%z1lm
zBPWA_z>y8N>tCK1Vr(v47<c>Kr2iir=8U2ti+bb|3Y4n$$q4TOr58_EKbLh*2~CI;
zG!>YF#JbtsZn7{ifG{Wp0TZL6uBV@yzN21Hu%kf}%T+c`tu~Lg@4SrcObeP=jsb-Z
zF)=b4avN~6v1;=%GfA;B7;qyLF)nCg`eM+;^ueHs@zw%nCPpSE5z#r7%RX}lt$la+
z0o%tJKEZ{$-@hX1VdOT@G*mZGWn&IyVdjymC`c~ONX^MnD9%jJOHENoEGQ_3<b$I8
z{1O8t34S93LsLUTBMSpChyn{(7#JCtLIn)83^feYAjZjKF%DuHm|p~Rrjh|K!Yz!f
z49rc8{0u;GE~X|%MuuG-fh%14#I8C1&D++&W+q^3BYgAK6H~7(Z@y-P=crU4Jg&Op
z!43Jn8|Sdi&Xa2Ul>KOZRB3irN+Czp(x~OG?cZgts?1@kJL~*MF7twgg68Da3Wq(P
z+}`PPC_9Jmt!NYf$GWwdQX4NbS#@hUzFS`KfS<SbZyLAu29-%mYIk-dR!-O$KEd{q
z_0b1AJmgoHU+!>zuP$~YbbDIVsye0#`c~Bf4QwiBWOAmjot7BA{n0d`1Eq$Gc_!Z7
za_ak^wX&+0Ip6OT=UblfaC5c3Qhw&+EnQO+W#6e?Q=PkdB8Q*ZX&Dt3o&93n`cFUh
z&lES@vH72;@~@)6BZgO**W8leGJV_2#LURRxR~F77Z_vAp|boeEX+*o4Jgsa3<@VR
zYb$r@#aawAxhuZ#ow}cyzpx|6-nGx5JytYdrQ70Z`IAX<Dk~qm%juq$3{be!Qm{ny
z;=Hf_Z&r(QwHr-7_Q)ph{-=+UNhh9f$vT*(P!~Lp@oLr5`=`Gy_Ho%ebK0LHvl6v`
z-1_wAZTXQ5*KNDy%`RGgJbE$hndfAa;}b7#Jrkd3rzw)SpNnIYs@JV)2OD@FHy8ik
zlkdI8-N;i*u*~e0z+t{i5>9W`er?Zjxc)4JcXEZ=a*@igd0dIp!{5(-C2e{7^tUBD
zU&ep`AuzwIOmfM_TMHh_XYZW6M5^h+)RnJhUjKT&_VUWF!IyTFRL?jW?AjiD(0OM1
z76G<%Ja!RY28@PHj4OeOW+^ap7&wxUpmAgmMDAK(Zy*5=Y))oF<jiKEjF4e56k`$j
z#pU$H^IB1NgsXiX$D1kNn^Mx2qa|;aCPoIO?Xq*L@7!6Ce#qtCsn<$(AG~-rL+?%I
z`gW&vtKRbevNfGp`Tc&ySr_^1LZ28O*EqNz6gjPaM@3=pqQVuOsZ}pa^PVy%{FEw-
z?D01XSmWpI;yLTN=<R}v=YQ;~z8zWpaHG(T7pz+!+Z{{4_Kam|EZ5I9ox8aF+@?kC
zJNh%H^ycK(OKfU(v-wO}!@q3VPbRAy-4RN;w=Cn{*#(sHy}#IG`_sbib#lJDsh4oV
zqEj&oQ$k`D1*Rxpu{rZs_uGSI^;Xk+U0z4G_ed$ebWbc-&f2YGlke2a*d6px=gJz^
z>coGmbeKafh8~Vn4bM`wui$^}I87=0#+R^%e}4VT?Em-u3Th$v@6G>2FCg0s+8jVC
z1cTjzfsvM2Qd*R%8wRP~trQIP47h?l91V3{GSh+4sOt-DELbV{q?Lx385f2n1C^$j
zc$J2jxEH4dS~^!cS=e%+TDq&?Yf2*{0|N+)AY1C|=;!I?8XThM>jpCz)oLz3U>n3r
z0j1Fba*vUz8Pr8q3Qi$;&IKWcrY3$yW<gQ87D*W?E+)p|5h(^GS*A(pX8CEphL(Ol
zo(BG|w#a_5Qiw_naY`$Q3UJTyF)qo>^ehd^GB9w<PBPCgw}n{^X||x-nG|V|V_8w?
z?iJ}=P#Br&W#DaAXyOtc8dz9rkn88<=4;|%lA4>5mFbm^W~W<8sE1>gMMQpKhN*K@
zNpiS_g-2P4c79=|FWgSlwhg-7t^rxueo^I>iIx%C1x2MkITh~OZjR1A<?hZV77?K)
z!It?^{*DGYVPS!2b{l6p<(ZYbWtykDd+P_~c==Trga%ZGWCxq)gQAR)NrV}9!x07=
z7#Yx;k?2~{n|=tbmGFijx*qiQ9YW6ujMg5qUTifxLN6CHdOeS>7d=BD^zw6{We;?%
d=*b$PwS@zFA`kFp1&0S45T*cinS&av3;^f$P_O_1
index 2c6e371c1fcb06361d7ca682039736151eb44fd7..1d2973196823842bf3ceb7a85f536e938f342dd6
GIT binary patch
literal 3011
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
index 4a3c13debcbb5e21f760c7f692f0d754a92acde1..29b92f4b9497fceaf37e65019f546154c0164625
GIT binary patch
literal 2882
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
index d556c06c84987aaf6cab1cdb2e766488a6cf3c87..66ef1037a7fc437f530579a32c96f87632d18662
GIT binary patch
literal 2936
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
index 6fe760615ac4f5816a4cb9772f717b166e4f2eae..b1b71768463dbf8d16f71fffb66125dd6d7da934
GIT binary patch
literal 2813
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
index 947616ebbeac123e45db4dfd617a184f37dcab81..6ac41ad7b3f8cbd521b59313ec02d5ddd57e1e8e
GIT binary patch
literal 3503
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
index c2ad381b16ebc6838cc9534abf20b891ff4c146d..b5cc693c3bce90fb8a994bf39884db9d10720c4e
GIT binary patch
literal 2901
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
Zt;;yDC$j)=R&aQ*0bvSI*KJM^4*;yvIotpM
index e88095e6eeed65f94c50d63d6e941c15ade58c1a..803e43098954ecbddcc73d9fcf4bc95bf54dd3ce
GIT binary patch
literal 2867
zc$^FHW@Zs#0D&of(cV%q8k_upY!K!J;@rf%%(T?v61}YA{Jd%|1qCITNPJmpQE_H|
zo|2V<kq$^AFEKY2$WaObN-DTkl%(c?rNMFtwMqth26~1{T(w*QC>DS7Fx1fovO$;)
zh=W`mU3^_bGBS%5fT$!RRiP*~F(o%uAuYcM%q;;~lM1s&56!sQO_uCpObiSl%m)-p
zNi0d!&&*9sPt_~POYaQubLZuf;sSZn)59eQNKXS{4v@_@jD?RF7#Qz*x;TbZ%z1lm
zBPWA_z>y8N>tCK1Vr(v47<c>Kr2iir=8U2ti+bb|3Y4n$$q4TOr58_EKbLh*2~DUe
z==4qQzMCuz3?K|jLBPc5sO#zHrthd16zpiw#B!C5Q>)FR?K>|cJJW(DmSaGnLrjc}
zhTI06Y^>UR%uG_O3<lf?MT`rYn7$Y^F?}#-V!XA0nTe5!Nknu`<+9J*L2KU~e!%u|
zhEH&z?)R@qdKkG4G!4}aRN0tAS(tg`DhiT|Gg5PM6pAy`^HNh35(^57A^D&vKflC4
zNrK<Vz|hpt(8$6745GjS76wKJrceO`Ekg|hHHdNYSd4?12Idz5ovCENi*O4gD+6;A
zBR>OBoQtW6k&$6nN8k#VKCx?#fAh9=u$c+i+6do#^~BU`%bTwm;W;YR2al_+cyL30
z@5VW7v-70dK4m{zA61&2l~Tx2wKQtEYx{SZt15Gt>drd<k;}Ybp`bZ=wZdV~C%1R{
z9Lmn&dn?++|FLdurqssEOjg}mj_;NiJmBZ;{hP+Ey+LKtlG>deiIo#JhEK4)WPS9(
z4iEVi=9fF1->Zw=2;H6*wW^M3g1%L?Km(h~8JV2vYo{efZ+|pR=s>CAVxEb2x19R^
zXRWO2WzP3I#rc+JJltHZuauwpcuUvRMA>(0*Hq`Op2*>6c3MV-MQ6WQxBk<Q{WHZ4
zcWnOWsr;)b@QC46<~6q@xJ=*nGBGnUFfQgd;04AQbEqso3kx$7djm@JF@wU%%-YIb
zda)M6Ozw&=e5dYb<}d8Xv3Kn=Xpa@mSLwERTK;5`oXX0_?sB@PB?A=hv=l56y*Tgd
z|C`m~T<u1ak3F)<yZ`B<WYUS}Te1$ODbxkeW4v0m^#19ui+x=7&Ybq=$gD)|AGbdJ
zd0T!Y!*$zkd9#a_ACF#4d*(UW<oLvkThGKN+G&a;?&spzr0R8R+QA0i$IZq6_vCx8
zaX0eR5-c-&C2*MUl7!P+wO`wF9IihL;hkKewp^q#Y#vwQ^ziqyUrAe@KK*UU&X@7u
ze+bO)DwABY@z#Qe^4U8lFOh1xFm>gtnb*Ibuf4qTYw)EVCDk)d2D`QgA9S9XzD0oT
z9FJXumjR<;6XQx?qFD;e90ragBxoGj1ChHH*c(W|1DliC5IM6MC?jN848>SPKD=Jr
zEOGQxV)yjzTz^X{R7|?&vY;hzmL^7qz9apYrdsr}PYPYEZJX~eEZ|jP$tJQ+S@bKv
zzWe88Z*O-`<PeB2;Fh~nD{yIoXffALmm3uu&OBZ+^Q;Bale;IGcl%8Xy7x5j^0G(S
zYpZjf76?WE{oi*%WZlVlYmU%8M_d+|GFvV6fAl0i|HZ`f^LH$Iw)xHC^i3sUKlR^8
zI)?N%&gz(=x>RK0B_ri)k(DWl8Vm}%yQ|h`Jy&<yyIN%3i8oJH$Ll-_er6mR{fg6C
zVctPMd*z)qljnX8<EZD-+Vtpw=<*h><CcBGDr=XBOpIg-j$9DhCb>d?ao-}dw^L5(
z$*4_xz<BV2cF@k8`nlDcYIa`d=<VxUE_cn#jf-RNngrBBP&Opl-wVk0f;I<`3c+Bv
zU|^&rmXsEy>V`q8cPj-$Jp-;_4@W~?m&|lvH0t_78w)_0qI6Sl*OCnHvJ7X}H1~+C
zKqtSDw92B4FcVv}y89LPjHSS=#Q?%0$d>v#`gyv!28ZbRy1@)awVKNh*aopuKxwpq
z++$>F26d5@f>TJIb3urqsfnMFSx{82MN&qJi-~b~M2bO4mT6MDS$>+Yp{1XXr-8q#
zEzCejV+7r#q)3At%ZfsGuSn;D!pKxF18=iJ6PNJNz`|04Tt6o_UlR|L)ZC1$Os{mf
zNvQ1*bQ4_zva<c6$}1BsBeV;ON_}!F+_l{toqfvPolPtvLQR4#^P~J74RXT50zrYs
z$RxsyyXgi44U7!v4LNkJ=#4Lg)=GF&3|$X;3k#v=1V(!cT`zjwiqOl&j9$~C>qSqJ
l2)+CqXbBWuD|*60Xl>!Zp3DNgS;67K281a<T{}5JJOBo99ESh^
index 945930f81c5aeaccaea7e87062286dadd3aa58e6..4d1a4d384340559282f19dcdab4d7ef0224e2380
GIT binary patch
literal 2831
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
GARYijNEWUD
index cccaa9993e2c10a513da6b6bed3918ec939a44e6..fe74ed666c6632eb1f63eca3cc85c6f92b759565
GIT binary patch
literal 2781
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
index f1cbb1d751edf9b2b4696c843a1f8df2f1a4b482..8d1de1552a45ad30d65699b8a19484270886fc54
GIT binary patch
literal 971
zc$^FHW@Zs#0D&of(cV%q8k_upY!K!J;@rf%%(T?v61}YA{Jd%|1qCITNPJmpQE_H|
zo|2V<kq$^AFEKY2$WaObN-DTkl%(c?rNMFtwMqth26~1{T(w*QC>DS7Fx1fovO$;)
zh=W`mU3^_bGBS%5fT$!RRiP*~F(o%uAuYcM%q;;~lM1s&56!sQO_uCpObiSl%m)-p
zNi0d!&&*9sPt_~POYaQubLZuf;sSZn)59eQNKXS{4v@_@jD?RF7#Qz*x;TbZ%z1lm
zBPWA_z>y8N>tCK1Vr(v47<c>Kr2iir=8U2ti+bb|3Y4n$$q4TOr58_EKbLh*2~7ds
zj7%cTxbp%GG%zxt=L~eM=&2c@wGy7T(e<DwS%jVw7zsGQn-y#<GZ3x@(p=0S9sqVk
Bf_(r0