bug 1357815 - 3/4: support SHA256 in PKCS#7 signatures on add-ons r=dveditz,jcj
authorDavid Keeler <dkeeler@mozilla.com>
Tue, 24 Oct 2017 15:27:53 -0700
changeset 389870 3b40cb8a242e5ef843c4cc587bcd4050c70e3a8a
parent 389869 89b22be1685c2bb3ff3b8ff3c9f5a0e6744b8bdb
child 389871 c5f4d4a711f9f6babd013049b382aaca51771ee8
push id54624
push userdkeeler@mozilla.com
push dateThu, 02 Nov 2017 17:07:14 +0000
treeherderautoland@c5f4d4a711f9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdveditz, jcj
bugs1357815
milestone58.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
bug 1357815 - 3/4: support SHA256 in PKCS#7 signatures on add-ons r=dveditz,jcj 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
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
index 2c6e371c1fcb06361d7ca682039736151eb44fd7..1d2973196823842bf3ceb7a85f536e938f342dd6
GIT binary patch
literal 3011
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
index 4a3c13debcbb5e21f760c7f692f0d754a92acde1..29b92f4b9497fceaf37e65019f546154c0164625
GIT binary patch
literal 2882
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
zXaj@d>YFSK3?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>SPCbC!j
zy3{M-<Q(PZSbjk7`^#^x<!H&9rHPS2_q=zd*yr@>8!rr}In=rF*UvoqCFF{Rw6=7h
z{<Qb^XRHlfo0a1&Rl{sH|F~Op$&cw1jg1#hT|WC>jBBt-V@F41RhDN<>7B-l+TSzT
z-_!;4Fs?4{y%qB!r<>!H^%Iq)xi-)CiQJ6+{{6c4V~MFp!-P(1Gke}?yY87*EA%Ar
zfsUBR^rrb2mTcrnNt&c=|4QonwByecCdn^4X}5Xy^K~Cto-Lf|`1!F_TG5*+AFDr~
z{#hP(;@ijB>l4~IAF|fHIq^U9&coC~>CzQ7W=g(->E`Qiw=rv57r*1{a?aqLH&g0r
zxvXl!jqATBN{R+Nx+tt(HpTSX^|^Z7>W<$gGxka7GNBfNrhC>L>j$#?q0IrLLNM4Z
z7#L}ZC8b5Fx?zy&-Achw&wwk~!_iRJB{Llujk><j#)6fCm#dj)u#1yva#oU)VU$T^
zVRCw8T40osOQ?x0NQ;rF8K$PlltK@8?TFxzVuP%bDw8k+ll0Ou?ZlwuMDwKJGH1(>
zB7@M#GIys;GoT@;E@w;LcJM5a4Z<SGF86ix^K^3!4$<><gIR>?el9;?L&i!0rR9U{
zDJzAj#1N;nf~WxZ93SJ7%uLVHpezFex9lYI{Bm2Ec1ZgLRi9f)sE1>gMMQpKhN*K@
zNpiS_g-2P4c79=|FI*pLLj_g8ai&wAS*cs5d8)g&eo&5=Uxh(vKxIgFuz5bn`;1H?
z%(z=}FwnrrfZnb{*NWa2Lujppx6IJ>pf|V>dQM<8ztHufSG5SeT+HYdFS=gz6pGNx
j&w-X+(Y2x{5ro#09M}^~fHx~RJlKFR1sIMeI6*uBc`7P@
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
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
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