summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorkerrnel <kerrnel@chromium.org>2015-11-02 15:40:53 -0800
committerCommit bot <commit-bot@chromium.org>2015-11-02 23:41:44 +0000
commit2b08c52ad7d79322756e93b7fb4dfb0a00f402e4 (patch)
tree9eeefc257bf01d001d694b496043b01b3109266e
parent42d9e3744b46e0fa1f555818273216e18094eeca (diff)
downloadchromium_src-2b08c52ad7d79322756e93b7fb4dfb0a00f402e4.zip
chromium_src-2b08c52ad7d79322756e93b7fb4dfb0a00f402e4.tar.gz
chromium_src-2b08c52ad7d79322756e93b7fb4dfb0a00f402e4.tar.bz2
Implement anonymous, opt-in, collection of OS X binary integrity incidents.
This change implements anonymous collection of data about binary integrity incidents on OS X. A binary integrity incident means that Chrome's application bundle was modified on disk by some unauthorized source. This change collects information about modified files. The checks are enabled on a sampling of user's browsers and run at browser startup if enabled. BUG=530314 Review URL: https://codereview.chromium.org/1363613004 Cr-Commit-Position: refs/heads/master@{#357469}
-rw-r--r--chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer.cc69
-rw-r--r--chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer.h13
-rw-r--r--chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer_mac.cc77
-rw-r--r--chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer_mac.h40
-rw-r--r--chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer_mac_unittest.cc139
-rw-r--r--chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer_win.cc45
-rw-r--r--chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer_win.h23
-rw-r--r--chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer_win_unittest.cc2
-rw-r--r--chrome/browser/safe_browsing/signature_evaluator_mac.h71
-rw-r--r--chrome/browser/safe_browsing/signature_evaluator_mac.mm235
-rw-r--r--chrome/browser/safe_browsing/signature_evaluator_mac_unittest.cc328
-rw-r--r--chrome/chrome_browser.gypi3
-rw-r--r--chrome/chrome_tests_unit.gypi2
-rw-r--r--chrome/common/safe_browsing/csd.proto26
-rw-r--r--chrome/test/data/safe_browsing/mach_o/Makefile44
-rw-r--r--chrome/test/data/safe_browsing/mach_o/ResourceRules99
-rw-r--r--chrome/test/data/safe_browsing/mach_o/base-bundle.app/Contents/Info.plist48
-rw-r--r--chrome/test/data/safe_browsing/mach_o/base-bundle.app/Contents/PkgInfo1
-rw-r--r--chrome/test/data/safe_browsing/mach_o/base-bundle.app/Contents/Resources/Base.lproj/MainMenu.nibbin0 -> 36966 bytes
-rw-r--r--chrome/test/data/safe_browsing/mach_o/modified-bundle-and-exec.app/Contents/Frameworks/libsigned64.dylibbin0 -> 4268 bytes
-rw-r--r--chrome/test/data/safe_browsing/mach_o/modified-bundle-and-exec.app/Contents/Info.plist48
-rw-r--r--chrome/test/data/safe_browsing/mach_o/modified-bundle-and-exec.app/Contents/MacOS/test-bundlebin0 -> 34192 bytes
-rw-r--r--chrome/test/data/safe_browsing/mach_o/modified-bundle-and-exec.app/Contents/PkgInfo1
-rw-r--r--chrome/test/data/safe_browsing/mach_o/modified-bundle-and-exec.app/Contents/Resources/Base.lproj/MainMenu.nibbin0 -> 36966 bytes
-rw-r--r--chrome/test/data/safe_browsing/mach_o/modified-bundle-and-exec.app/Contents/Resources/codesign.cfg0
-rw-r--r--chrome/test/data/safe_browsing/mach_o/modified-bundle-and-exec.app/Contents/Resources/executable32bin0 -> 4352 bytes
-rw-r--r--chrome/test/data/safe_browsing/mach_o/modified-bundle-and-exec.app/Contents/_CodeSignature/CodeResources139
-rw-r--r--chrome/test/data/safe_browsing/mach_o/modified-bundle.app/Contents/Frameworks/libsigned64.dylibbin0 -> 4268 bytes
-rw-r--r--chrome/test/data/safe_browsing/mach_o/modified-bundle.app/Contents/Info.plist48
-rw-r--r--chrome/test/data/safe_browsing/mach_o/modified-bundle.app/Contents/MacOS/test-bundlebin0 -> 34192 bytes
-rw-r--r--chrome/test/data/safe_browsing/mach_o/modified-bundle.app/Contents/PkgInfo1
-rw-r--r--chrome/test/data/safe_browsing/mach_o/modified-bundle.app/Contents/Resources/Base.lproj/MainMenu.nibbin0 -> 36970 bytes
-rw-r--r--chrome/test/data/safe_browsing/mach_o/modified-bundle.app/Contents/Resources/codesign.cfg0
-rw-r--r--chrome/test/data/safe_browsing/mach_o/modified-bundle.app/Contents/Resources/executable32bin0 -> 4352 bytes
-rw-r--r--chrome/test/data/safe_browsing/mach_o/modified-bundle.app/Contents/_CodeSignature/CodeResources139
-rw-r--r--chrome/test/data/safe_browsing/mach_o/modified-main-exec32.app/Contents/Frameworks/libsigned64.dylibbin0 -> 13632 bytes
-rw-r--r--chrome/test/data/safe_browsing/mach_o/modified-main-exec32.app/Contents/Info.plist48
-rw-r--r--chrome/test/data/safe_browsing/mach_o/modified-main-exec32.app/Contents/MacOS/test-bundlebin0 -> 34192 bytes
-rw-r--r--chrome/test/data/safe_browsing/mach_o/modified-main-exec32.app/Contents/PkgInfo1
-rw-r--r--chrome/test/data/safe_browsing/mach_o/modified-main-exec32.app/Contents/Resources/Base.lproj/MainMenu.nibbin0 -> 36966 bytes
-rw-r--r--chrome/test/data/safe_browsing/mach_o/modified-main-exec32.app/Contents/Resources/executable32bin0 -> 4324 bytes
-rw-r--r--chrome/test/data/safe_browsing/mach_o/modified-main-exec32.app/Contents/_CodeSignature/CodeResources139
-rw-r--r--chrome/test/data/safe_browsing/mach_o/modified-main-exec64.app/Contents/Frameworks/libsigned64.dylibbin0 -> 13632 bytes
-rw-r--r--chrome/test/data/safe_browsing/mach_o/modified-main-exec64.app/Contents/Info.plist48
-rw-r--r--chrome/test/data/safe_browsing/mach_o/modified-main-exec64.app/Contents/MacOS/test-bundlebin0 -> 34192 bytes
-rw-r--r--chrome/test/data/safe_browsing/mach_o/modified-main-exec64.app/Contents/PkgInfo1
-rw-r--r--chrome/test/data/safe_browsing/mach_o/modified-main-exec64.app/Contents/Resources/Base.lproj/MainMenu.nibbin0 -> 36966 bytes
-rw-r--r--chrome/test/data/safe_browsing/mach_o/modified-main-exec64.app/Contents/Resources/executable32bin0 -> 4324 bytes
-rw-r--r--chrome/test/data/safe_browsing/mach_o/modified-main-exec64.app/Contents/_CodeSignature/CodeResources139
-rw-r--r--chrome/test/data/safe_browsing/mach_o/test-bundle.app/Contents/Frameworks/libsigned64.dylibbin0 -> 13632 bytes
-rw-r--r--chrome/test/data/safe_browsing/mach_o/test-bundle.app/Contents/Info.plist48
-rw-r--r--chrome/test/data/safe_browsing/mach_o/test-bundle.app/Contents/MacOS/test-bundlebin0 -> 34192 bytes
-rw-r--r--chrome/test/data/safe_browsing/mach_o/test-bundle.app/Contents/PkgInfo1
-rw-r--r--chrome/test/data/safe_browsing/mach_o/test-bundle.app/Contents/Resources/Base.lproj/MainMenu.nibbin0 -> 36966 bytes
-rw-r--r--chrome/test/data/safe_browsing/mach_o/test-bundle.app/Contents/Resources/executable32bin0 -> 4324 bytes
-rw-r--r--chrome/test/data/safe_browsing/mach_o/test-bundle.app/Contents/_CodeSignature/CodeResources139
56 files changed, 2143 insertions, 62 deletions
diff --git a/chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer.cc b/chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer.cc
index d12d770..1768187 100644
--- a/chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer.cc
+++ b/chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer.cc
@@ -18,32 +18,35 @@
#include "chrome/browser/safe_browsing/incident_reporting/binary_integrity_incident.h"
#include "chrome/browser/safe_browsing/incident_reporting/incident_receiver.h"
#include "chrome/browser/safe_browsing/safe_browsing_service.h"
-#include "chrome/common/safe_browsing/binary_feature_extractor.h"
#include "chrome/common/safe_browsing/csd.pb.h"
namespace safe_browsing {
-namespace {
-
void RecordSignatureVerificationTime(size_t file_index,
- const base::TimeDelta& verification_time) {
+ const base::TimeDelta& verification_time) {
static const char kHistogramName[] = "SBIRS.VerifyBinaryIntegrity.";
base::HistogramBase* signature_verification_time_histogram =
base::Histogram::FactoryTimeGet(
std::string(kHistogramName) + base::SizeTToString(file_index),
base::TimeDelta::FromMilliseconds(1),
- base::TimeDelta::FromSeconds(20),
- 50,
+ base::TimeDelta::FromSeconds(20), 50,
base::Histogram::kUmaTargetedHistogramFlag);
- signature_verification_time_histogram->AddTime(verification_time);
+ signature_verification_time_histogram->AddTime(verification_time);
}
-} // namespace
+void ClearBinaryIntegrityForFile(IncidentReceiver* incident_receiver,
+ const std::string& basename) {
+ scoped_ptr<ClientIncidentReport_IncidentData_BinaryIntegrityIncident>
+ incident(new ClientIncidentReport_IncidentData_BinaryIntegrityIncident());
+ incident->set_file_basename(basename);
+ incident_receiver->ClearIncidentForProcess(
+ make_scoped_ptr(new BinaryIntegrityIncident(incident.Pass())));
+}
void RegisterBinaryIntegrityAnalysis() {
-#if defined(OS_WIN)
+#if defined(OS_WIN) || defined(OS_MACOSX)
scoped_refptr<SafeBrowsingService> safe_browsing_service(
g_browser_process->safe_browsing_service());
@@ -52,52 +55,4 @@ void RegisterBinaryIntegrityAnalysis() {
#endif
}
-void VerifyBinaryIntegrity(scoped_ptr<IncidentReceiver> incident_receiver) {
- scoped_refptr<BinaryFeatureExtractor> binary_feature_extractor(
- new BinaryFeatureExtractor());
-
- std::vector<base::FilePath> critical_binaries = GetCriticalBinariesPath();
- for (size_t i = 0; i < critical_binaries.size(); ++i) {
- base::FilePath binary_path(critical_binaries[i]);
- if (!base::PathExists(binary_path))
- continue;
-
- scoped_ptr<ClientDownloadRequest_SignatureInfo> signature_info(
- new ClientDownloadRequest_SignatureInfo());
-
- base::TimeTicks time_before = base::TimeTicks::Now();
- binary_feature_extractor->CheckSignature(binary_path, signature_info.get());
- RecordSignatureVerificationTime(i, base::TimeTicks::Now() - time_before);
-
- // Only create a report if the signature is untrusted.
- if (!signature_info->trusted()) {
- scoped_ptr<ClientIncidentReport_IncidentData_BinaryIntegrityIncident>
- incident(
- new ClientIncidentReport_IncidentData_BinaryIntegrityIncident());
-
- incident->set_file_basename(binary_path.BaseName().AsUTF8Unsafe());
- incident->set_allocated_signature(signature_info.release());
-
- // Send the report.
- incident_receiver->AddIncidentForProcess(
- make_scoped_ptr(new BinaryIntegrityIncident(incident.Pass())));
- } else {
- // The binary is integral, remove previous report so that next incidents
- // for the binary will be reported.
- scoped_ptr<ClientIncidentReport_IncidentData_BinaryIntegrityIncident>
- incident(
- new ClientIncidentReport_IncidentData_BinaryIntegrityIncident());
- incident->set_file_basename(binary_path.BaseName().AsUTF8Unsafe());
- incident_receiver->ClearIncidentForProcess(
- make_scoped_ptr(new BinaryIntegrityIncident(incident.Pass())));
- }
- }
-}
-
-#if !defined(OS_WIN)
-std::vector<base::FilePath> GetCriticalBinariesPath() {
- return std::vector<base::FilePath>();
-}
-#endif // !defined(OS_WIN)
-
} // namespace safe_browsing
diff --git a/chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer.h b/chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer.h
index 6ac4532..0d1efe7 100644
--- a/chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer.h
+++ b/chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer.h
@@ -5,12 +5,13 @@
#ifndef CHROME_BROWSER_SAFE_BROWSING_INCIDENT_REPORTING_BINARY_INTEGRITY_ANALYZER_H_
#define CHROME_BROWSER_SAFE_BROWSING_INCIDENT_REPORTING_BINARY_INTEGRITY_ANALYZER_H_
-#include <vector>
+#include <string>
#include "base/memory/scoped_ptr.h"
namespace base {
class FilePath;
+class TimeDelta;
} // namespace base
namespace safe_browsing {
@@ -26,8 +27,14 @@ void RegisterBinaryIntegrityAnalysis();
// service will decide when to start the analysis.
void VerifyBinaryIntegrity(scoped_ptr<IncidentReceiver> incident_receiver);
-// Returns a vector containing the paths to all the binaries to verify.
-std::vector<base::FilePath> GetCriticalBinariesPath();
+// Record how long the signature verification took.
+void RecordSignatureVerificationTime(size_t file_index,
+ const base::TimeDelta& verification_time);
+
+// Clear past incident reports for a file or bundle. This is used if the code
+// object is now integral, as it will allow future incidents to be reported.
+void ClearBinaryIntegrityForFile(IncidentReceiver* incident_receiver,
+ const std::string& basename);
} // namespace safe_browsing
diff --git a/chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer_mac.cc b/chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer_mac.cc
new file mode 100644
index 0000000..b6da848
--- /dev/null
+++ b/chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer_mac.cc
@@ -0,0 +1,77 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer_mac.h"
+
+#include "base/files/file_util.h"
+#include "base/mac/bundle_locations.h"
+#include "chrome/browser/safe_browsing/incident_reporting/binary_integrity_incident.h"
+#include "chrome/browser/safe_browsing/incident_reporting/incident_receiver.h"
+#include "chrome/browser/safe_browsing/signature_evaluator_mac.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
+
+#define DEVELOPER_ID_APPLICATION_OID "field.1.2.840.113635.100.6.1.13"
+#define DEVELOPER_ID_INTERMEDIATE_OID "field.1.2.840.113635.100.6.2.6"
+
+namespace safe_browsing {
+
+namespace {
+
+void VerifyBinaryIntegrityHelper(IncidentReceiver* incident_receiver,
+ const base::FilePath& path,
+ const std::string& requirement) {
+ MacSignatureEvaluator evaluator(path, requirement);
+ if (!evaluator.Initialize()) {
+ LOG(ERROR) << "Could not initialize mac signature evaluator";
+ return;
+ }
+
+ scoped_ptr<ClientIncidentReport_IncidentData_BinaryIntegrityIncident>
+ incident(new ClientIncidentReport_IncidentData_BinaryIntegrityIncident());
+ if (!evaluator.PerformEvaluation(incident.get())) {
+ incident_receiver->AddIncidentForProcess(
+ make_scoped_ptr(new BinaryIntegrityIncident(incident.Pass())));
+ } else {
+ // Clear past incidents involving this bundle if the signature is
+ // now valid.
+ ClearBinaryIntegrityForFile(incident_receiver, path.BaseName().value());
+ }
+}
+
+} // namespace
+
+std::vector<PathAndRequirement> GetCriticalPathsAndRequirements() {
+ // Get the path to the main executable.
+ std::vector<PathAndRequirement> critical_binaries;
+ // This requirement describes a developer ID signed application,
+ // with Google's team identifier, and the com.Google.Chrome[.canary]
+ // identifier.
+ std::string requirement =
+ "anchor apple generic and certificate 1[" DEVELOPER_ID_INTERMEDIATE_OID
+ "] exists and certificate leaf[" DEVELOPER_ID_APPLICATION_OID
+ "] exists and certificate leaf[subject.OU]=\"EQHXZ8M8AV\" and "
+ "(identifier=\"com.google.Chrome\" or "
+ "identifier=\"com.google.Chrome.canary\")";
+ critical_binaries.push_back(
+ PathAndRequirement(base::mac::OuterBundlePath(), requirement));
+ // TODO(kerrnel): eventually add Adobe Flash Player to this list.
+ return critical_binaries;
+}
+
+void VerifyBinaryIntegrityForTesting(IncidentReceiver* incident_receiver,
+ const base::FilePath& path,
+ const std::string& requirement) {
+ VerifyBinaryIntegrityHelper(incident_receiver, path, requirement);
+}
+
+void VerifyBinaryIntegrity(scoped_ptr<IncidentReceiver> incident_receiver) {
+ size_t i = 0;
+ for (const auto& p : GetCriticalPathsAndRequirements()) {
+ base::TimeTicks time_before = base::TimeTicks::Now();
+ VerifyBinaryIntegrityHelper(incident_receiver.get(), p.path, p.requirement);
+ RecordSignatureVerificationTime(i++, base::TimeTicks::Now() - time_before);
+ }
+}
+
+} // namespace
diff --git a/chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer_mac.h b/chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer_mac.h
new file mode 100644
index 0000000..05ecec2
--- /dev/null
+++ b/chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer_mac.h
@@ -0,0 +1,40 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_SAFE_BROWSING_INCIDENT_REPORTING_BINARY_INTEGRITY_ANALYZER_MAC_H_
+#define CHROME_BROWSER_SAFE_BROWSING_INCIDENT_REPORTING_BINARY_INTEGRITY_ANALYZER_MAC_H_
+
+#include "chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer.h"
+
+#include <string>
+#include <vector>
+
+#include "base/files/file_path.h"
+
+namespace safe_browsing {
+
+class IncidentReceiver;
+
+// Wraps a path to a code object and its specified code requirement.
+struct PathAndRequirement {
+ PathAndRequirement(const base::FilePath& o_path,
+ const std::string& o_requirement)
+ : path(o_path), requirement(o_requirement) {}
+ base::FilePath path;
+ std::string requirement;
+};
+
+// Returns a vector of pairs, each of which contains the paths to the binaries
+// to verify, and the codesign requirement to use when verifying.
+std::vector<PathAndRequirement> GetCriticalPathsAndRequirements();
+
+// This is a helper stub to allow the signature checking code to be tested with
+// custom requirements and files.
+void VerifyBinaryIntegrityForTesting(IncidentReceiver* incident_receiver,
+ const base::FilePath& path,
+ const std::string& requirement);
+
+} // namespace safe_browsing
+
+#endif // CHROME_BROWSER_SAFE_BROWSING_INCIDENT_REPORTING_BINARY_INTEGRITY_ANALYZER_MAC_H_
diff --git a/chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer_mac_unittest.cc b/chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer_mac_unittest.cc
new file mode 100644
index 0000000..8d3c00a
--- /dev/null
+++ b/chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer_mac_unittest.cc
@@ -0,0 +1,139 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer_mac.h"
+
+#include "base/files/file.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/mac/bundle_locations.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/path_service.h"
+#include "chrome/browser/safe_browsing/incident_reporting/incident.h"
+#include "chrome/browser/safe_browsing/incident_reporting/mock_incident_receiver.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::_;
+using ::testing::StrictMock;
+
+namespace safe_browsing {
+
+namespace {
+
+const char kBundleBase[] = "test-bundle.app";
+const char kBundleURL[] = "test-bundle.app/Contents/MacOS/test-bundle";
+
+// Helper function to corrupt the content of a binary to make sure the signature
+// verification will fail.
+bool CorruptFileContent(const base::FilePath& file_path) {
+ // This is the hard coded position of the TEXT segment in the mach-o file.
+ static const uint64_t text_pos = 0x1F90;
+ base::File file(file_path, base::File::FLAG_OPEN | base::File::FLAG_WRITE);
+ if (!file.IsValid())
+ return false;
+ char vec[] = {0xAA};
+ return file.Write(text_pos, vec, sizeof(vec)) == sizeof(vec);
+}
+
+} // namespace
+
+class BinaryIntegrityAnalyzerMacTest : public ::testing::Test {
+ public:
+ void SetUp() override;
+
+ protected:
+ base::FilePath test_data_dir_;
+ base::ScopedTempDir temp_dir_;
+};
+
+void BinaryIntegrityAnalyzerMacTest::SetUp() {
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir_));
+ test_data_dir_ = test_data_dir_.Append("safe_browsing/mach_o/");
+
+ // Set up the temp directory to copy the bundle to.
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+
+ // Copy the signed bundle to the temp directory.
+ base::FilePath signed_bundle_path =
+ base::FilePath(test_data_dir_).Append(kBundleBase);
+ base::FilePath copied_bundle_path =
+ base::FilePath(temp_dir_.path()).Append(kBundleBase);
+ ASSERT_TRUE(
+ base::CopyDirectory(signed_bundle_path, copied_bundle_path, true));
+}
+
+TEST_F(BinaryIntegrityAnalyzerMacTest, GetCriticalPathsAndRequirements) {
+ // Expected paths and requirement strings.
+ std::vector<PathAndRequirement> paths_and_requirements_expected;
+
+ std::string expected_req =
+ "anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] "
+ "exists and certificate leaf[field.1.2.840.113635.100.6.1.13] exists and "
+ "certificate leaf[subject.OU]=\"EQHXZ8M8AV\" and "
+ "(identifier=\"com.google.Chrome\" or "
+ "identifier=\"com.google.Chrome.canary\")";
+ paths_and_requirements_expected.push_back(
+ PathAndRequirement(base::mac::OuterBundlePath(), expected_req));
+
+ std::vector<PathAndRequirement> paths_and_requirements =
+ GetCriticalPathsAndRequirements();
+ ASSERT_EQ(1u, paths_and_requirements.size());
+ EXPECT_EQ(paths_and_requirements[0].path,
+ paths_and_requirements_expected[0].path);
+ EXPECT_EQ(paths_and_requirements[0].requirement,
+ paths_and_requirements_expected[0].requirement);
+}
+
+TEST_F(BinaryIntegrityAnalyzerMacTest, VerifyBinaryIntegrityForTesting) {
+ scoped_ptr<MockIncidentReceiver> mock_receiver(
+ new StrictMock<MockIncidentReceiver>());
+ base::FilePath bundle = temp_dir_.path().Append(kBundleBase);
+ std::string requirement(
+ "certificate leaf[subject.CN]=\"untrusted@goat.local\"");
+
+ // Run check on valid bundle.
+ scoped_ptr<Incident> incident_to_clear;
+ EXPECT_CALL(*mock_receiver, DoClearIncidentForProcess(_))
+ .WillOnce(TakeIncident(&incident_to_clear));
+ VerifyBinaryIntegrityForTesting(mock_receiver.get(), bundle, requirement);
+
+ ASSERT_TRUE(incident_to_clear);
+ ASSERT_EQ(IncidentType::BINARY_INTEGRITY, incident_to_clear->GetType());
+ ASSERT_EQ(incident_to_clear->GetKey(), "test-bundle.app");
+
+ base::FilePath exe_path = temp_dir_.path().Append(kBundleURL);
+ ASSERT_TRUE(CorruptFileContent(exe_path));
+
+ scoped_ptr<Incident> incident;
+ EXPECT_CALL(*mock_receiver, DoAddIncidentForProcess(_))
+ .WillOnce(TakeIncident(&incident));
+
+ VerifyBinaryIntegrityForTesting(mock_receiver.get(), bundle, requirement);
+
+ // Verify that the incident report contains the expected data.
+ scoped_ptr<ClientIncidentReport_IncidentData> incident_data(
+ incident->TakePayload());
+
+ ASSERT_TRUE(incident_data->has_binary_integrity());
+ EXPECT_TRUE(incident_data->binary_integrity().has_file_basename());
+ EXPECT_EQ("test-bundle.app",
+ incident_data->binary_integrity().file_basename());
+ EXPECT_TRUE(incident_data->binary_integrity().has_sec_error());
+ EXPECT_EQ(-67061, incident_data->binary_integrity().sec_error());
+ EXPECT_FALSE(incident_data->binary_integrity().has_signature());
+ EXPECT_EQ(0,
+ incident_data->binary_integrity().signature().signed_data_size());
+ EXPECT_EQ(1, incident_data->binary_integrity().contained_file_size());
+
+ const auto& contained_file =
+ incident_data->binary_integrity().contained_file(0);
+ EXPECT_EQ(contained_file.relative_path(), "Contents/MacOS/test-bundle");
+ EXPECT_TRUE(contained_file.has_signature());
+ EXPECT_TRUE(contained_file.has_image_headers());
+}
+
+} // namespace safe_browsing
diff --git a/chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer_win.cc b/chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer_win.cc
index e2013cb..54148e3 100644
--- a/chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer_win.cc
+++ b/chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer_win.cc
@@ -2,12 +2,17 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer.h"
+#include "chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer_win.h"
#include "base/files/file_path.h"
+#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/path_service.h"
+#include "chrome/browser/safe_browsing/incident_reporting/binary_integrity_incident.h"
+#include "chrome/browser/safe_browsing/incident_reporting/incident_receiver.h"
#include "chrome/common/chrome_version.h"
+#include "chrome/common/safe_browsing/binary_feature_extractor.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
namespace safe_browsing {
@@ -43,4 +48,42 @@ std::vector<base::FilePath> GetCriticalBinariesPath() {
return critical_binaries;
}
+void VerifyBinaryIntegrity(scoped_ptr<IncidentReceiver> incident_receiver) {
+ scoped_refptr<BinaryFeatureExtractor> binary_feature_extractor(
+ new BinaryFeatureExtractor());
+
+ std::vector<base::FilePath> critical_binaries = GetCriticalBinariesPath();
+ for (size_t i = 0; i < critical_binaries.size(); ++i) {
+ base::FilePath binary_path(critical_binaries[i]);
+ if (!base::PathExists(binary_path))
+ continue;
+
+ scoped_ptr<ClientDownloadRequest_SignatureInfo> signature_info(
+ new ClientDownloadRequest_SignatureInfo());
+
+ base::TimeTicks time_before = base::TimeTicks::Now();
+ binary_feature_extractor->CheckSignature(binary_path, signature_info.get());
+ RecordSignatureVerificationTime(i, base::TimeTicks::Now() - time_before);
+
+ // Only create a report if the signature is untrusted.
+ if (!signature_info->trusted()) {
+ scoped_ptr<ClientIncidentReport_IncidentData_BinaryIntegrityIncident>
+ incident(
+ new ClientIncidentReport_IncidentData_BinaryIntegrityIncident());
+
+ incident->set_file_basename(binary_path.BaseName().AsUTF8Unsafe());
+ incident->set_allocated_signature(signature_info.release());
+
+ // Send the report.
+ incident_receiver->AddIncidentForProcess(
+ make_scoped_ptr(new BinaryIntegrityIncident(incident.Pass())));
+ } else {
+ // The binary is integral, remove previous report so that next incidents
+ // for the binary will be reported.
+ ClearBinaryIntegrityForFile(incident_receiver.get(),
+ binary_path.BaseName().AsUTF8Unsafe());
+ }
+ }
+}
+
} // namespace safe_browsing
diff --git a/chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer_win.h b/chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer_win.h
new file mode 100644
index 0000000..6405ead
--- /dev/null
+++ b/chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer_win.h
@@ -0,0 +1,23 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_SAFE_BROWSING_INCIDENT_REPORTING_BINARY_INTEGRITY_ANALYZER_WIN_H_
+#define CHROME_BROWSER_SAFE_BROWSING_INCIDENT_REPORTING_BINARY_INTEGRITY_ANALYZER_WIN_H_
+
+#include "chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer.h"
+
+#include <vector>
+
+namespace base {
+class FilePath;
+} // namespace base
+
+namespace safe_browsing {
+
+// Returns a vector containing the paths to all the binaries to verify.
+std::vector<base::FilePath> GetCriticalBinariesPath();
+
+} // namespace safe_browsing
+
+#endif // CHROME_BROWSER_SAFE_BROWSING_INCIDENT_REPORTING_BINARY_INTEGRITY_ANALYZER_WIN_H_
diff --git a/chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer_win_unittest.cc b/chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer_win_unittest.cc
index b2055f1..dc7c9a3 100644
--- a/chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer_win_unittest.cc
+++ b/chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer_win_unittest.cc
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer.h"
+#include "chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer_win.h"
#include "base/bind.h"
#include "base/files/file_util.h"
diff --git a/chrome/browser/safe_browsing/signature_evaluator_mac.h b/chrome/browser/safe_browsing/signature_evaluator_mac.h
new file mode 100644
index 0000000..d14ba14
--- /dev/null
+++ b/chrome/browser/safe_browsing/signature_evaluator_mac.h
@@ -0,0 +1,71 @@
+// Copyright (c) 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_SAFE_BROWSING_SIGNATURE_EVALUATOR_MAC_H_
+#define CHROME_COMMON_SAFE_BROWSING_SIGNATURE_EVALUATOR_MAC_H_
+
+#include <Security/Security.h>
+
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "chrome/browser/safe_browsing/incident_reporting/binary_integrity_incident.h"
+
+namespace safe_browsing {
+
+// Wraps the OS X SecStaticCode API, to evaluate a given file object
+// with a given code requirement, and produce a list of incident reports
+// for files that fail code signature validity checks.
+class MacSignatureEvaluator {
+ public:
+ explicit MacSignatureEvaluator(const base::FilePath& signed_object_path);
+
+ // The requirement string must be a valid "Code Signing Requirement Language
+ // string, which describes the identity of the signer.
+ MacSignatureEvaluator(const base::FilePath& signed_object_path,
+ const std::string& requirement);
+
+ ~MacSignatureEvaluator();
+
+ // Creates the static code object and requirement string, and returns
+ // true if the object creation succeeds, else false.
+ bool Initialize();
+
+ // Evaluate the signature and return a list of any binary integrity incident
+ // reports. Returns true if and only if the signed code object is valid.
+ bool PerformEvaluation(
+ ClientIncidentReport_IncidentData_BinaryIntegrityIncident* incident);
+
+ // Returns relative path component between a parent and a child.
+ // For example, /foo/bar and /foo/bar/y returns y. Note that
+ // this knows nothing about symlinks. Exposed for testing.
+ static bool GetRelativePathComponent(const base::FilePath& parent,
+ const base::FilePath& child,
+ std::string* out);
+
+ private:
+ // The path to the code object on disk.
+ base::FilePath path_;
+
+ // A Code Signing Requirement string.
+ std::string requirement_str_;
+
+ // Records whether or not a requirement string was specified.
+ bool has_requirement_;
+
+ // The static code object constructed from the code object on disk.
+ base::ScopedCFTypeRef<SecStaticCodeRef> code_;
+
+ // The requirement object constructed from the requirement string.
+ base::ScopedCFTypeRef<SecRequirementRef> requirement_;
+
+ DISALLOW_COPY_AND_ASSIGN(MacSignatureEvaluator);
+};
+
+} // namespace safe_browsing
+
+#endif // CHROME_COMMON_SAFE_BROWSING_SIGNATURE_EVALUATOR_MAC_H_
diff --git a/chrome/browser/safe_browsing/signature_evaluator_mac.mm b/chrome/browser/safe_browsing/signature_evaluator_mac.mm
new file mode 100644
index 0000000..3189619
--- /dev/null
+++ b/chrome/browser/safe_browsing/signature_evaluator_mac.mm
@@ -0,0 +1,235 @@
+// Copyright (c) 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/safe_browsing/signature_evaluator_mac.h"
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <Foundation/Foundation.h>
+#include <Security/Security.h>
+#include <sys/xattr.h>
+
+#include "base/mac/foundation_util.h"
+#include "base/mac/mac_util.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "base/mac/scoped_nsobject.h"
+#include "base/strings/sys_string_conversions.h"
+#include "chrome/common/safe_browsing/binary_feature_extractor.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
+#include "chrome/common/safe_browsing/mach_o_image_reader_mac.h"
+
+namespace safe_browsing {
+
+namespace {
+
+// OS X code signing data can be stored in extended attributes as well. This is
+// a list of the extended attributes slots currently used in Security.framework,
+// from codesign.h (see the kSecCS_* constants).
+const char* const xattrs[] = {
+ "com.apple.cs.CodeDirectory",
+ "com.apple.cs.CodeSignature",
+ "com.apple.cs.CodeRequirements",
+ "com.apple.cs.CodeResources",
+ "com.apple.cs.CodeApplication",
+ "com.apple.cs.CodeEntitlements",
+};
+
+// Convenience function to get the appropriate path from a variety of NSObject
+// types. For resources, code signing seems to give back an NSURL in which
+// the path is relative to the bundle root. So in this case, we take the
+// relative component, otherwise we take the entire path.
+bool GetPathFromNSObject(id obj, std::string* output) {
+ if (NSString* str = base::mac::ObjCCast<NSString>(obj)) {
+ output->assign([str fileSystemRepresentation]);
+ return true;
+ }
+ if (NSURL* url = base::mac::ObjCCast<NSURL>(obj)) {
+ output->assign([[url path] fileSystemRepresentation]);
+ return true;
+ }
+ if (NSBundle* bundle = base::mac::ObjCCast<NSBundle>(obj)) {
+ output->assign([[bundle bundlePath] fileSystemRepresentation]);
+ return true;
+ }
+ return false;
+}
+
+// Extract the signature information from the mach-o or extended attributes.
+void ExtractSignatureInfo(const base::FilePath& path,
+ ClientDownloadRequest_ImageHeaders* image_headers,
+ ClientDownloadRequest_SignatureInfo* signature) {
+ scoped_refptr<BinaryFeatureExtractor> bfe = new BinaryFeatureExtractor();
+
+ // TODO(kerrnel): if Chrome ever opts into the OS X "kill" semantics, this
+ // call has to change. `ExtractImageFeatures` maps the file, which will
+ // cause Chrome to be killed before it can report on the invalid file.
+ // This call will need to read(2) the binary into a buffer.
+ if (!bfe->ExtractImageFeatures(path, BinaryFeatureExtractor::kDefaultOptions,
+ image_headers,
+ signature->mutable_signed_data())) {
+ // If this is not a mach-o file, search inside the extended attributes.
+ for (const char* attr : xattrs) {
+ ssize_t size = getxattr(path.value().c_str(), attr, nullptr, 0, 0, 0);
+ if (size >= 0) {
+ std::vector<uint8_t> xattr_data(size);
+ ssize_t post_size = getxattr(path.value().c_str(), attr, &xattr_data[0],
+ xattr_data.size(), 0, 0);
+ if (post_size >= 0) {
+ xattr_data.resize(post_size);
+ ClientDownloadRequest_ExtendedAttr* xattr_msg =
+ signature->add_xattr();
+ xattr_msg->set_key(attr);
+ xattr_msg->set_value(xattr_data.data(), xattr_data.size());
+ }
+ }
+ }
+ }
+}
+
+// Process the NSError information about any files that were altered.
+void ReportAlteredFiles(
+ id detail,
+ const base::FilePath& bundle_path,
+ ClientIncidentReport_IncidentData_BinaryIntegrityIncident* incident) {
+ if (NSArray* arr = base::mac::ObjCCast<NSArray>(detail)) {
+ for (id obj in arr)
+ ReportAlteredFiles(obj, bundle_path, incident);
+ } else {
+ std::string path_str;
+ if (!GetPathFromNSObject(detail, &path_str))
+ return;
+ std::string relative_path;
+ base::FilePath path(path_str);
+ // If the relative path calculation fails, at least take the basename.
+ if (!MacSignatureEvaluator::GetRelativePathComponent(bundle_path, path,
+ &relative_path)) {
+ relative_path = path.BaseName().value();
+ }
+
+ ClientIncidentReport_IncidentData_BinaryIntegrityIncident_ContainedFile*
+ contained_file = incident->add_contained_file();
+ contained_file->set_relative_path(relative_path);
+ ExtractSignatureInfo(base::FilePath(path_str),
+ contained_file->mutable_image_headers(),
+ contained_file->mutable_signature());
+ }
+}
+
+} // namespace
+
+MacSignatureEvaluator::MacSignatureEvaluator(
+ const base::FilePath& signed_object_path)
+ : path_(signed_object_path),
+ requirement_str_(),
+ has_requirement_(false),
+ code_(nullptr),
+ requirement_(nullptr) {}
+
+MacSignatureEvaluator::MacSignatureEvaluator(
+ const base::FilePath& signed_object_path,
+ const std::string& requirement)
+ : path_(signed_object_path),
+ requirement_str_(requirement),
+ has_requirement_(true),
+ code_(nullptr),
+ requirement_(nullptr) {}
+
+MacSignatureEvaluator::~MacSignatureEvaluator() {}
+
+bool MacSignatureEvaluator::GetRelativePathComponent(
+ const base::FilePath& parent,
+ const base::FilePath& child,
+ std::string* out) {
+ if (!parent.IsParent(child))
+ return false;
+
+ std::vector<base::FilePath::StringType> parent_components;
+ std::vector<base::FilePath::StringType> child_components;
+ parent.GetComponents(&parent_components);
+ child.GetComponents(&child_components);
+
+ size_t i = 0;
+ while (i < parent_components.size() &&
+ child_components[i] == parent_components[i]) {
+ ++i;
+ }
+
+ while (i < child_components.size()) {
+ out->append(child_components[i]);
+ if (++i < child_components.size())
+ out->append("/");
+ }
+ return true;
+}
+
+bool MacSignatureEvaluator::Initialize() {
+ base::scoped_nsobject<NSURL> code_url([[NSURL alloc]
+ initFileURLWithPath:base::SysUTF8ToNSString(path_.value())]);
+ if (!code_url)
+ return false;
+
+ if (SecStaticCodeCreateWithPath(base::mac::NSToCFCast(code_url.get()),
+ kSecCSDefaultFlags,
+ code_.InitializeInto()) != errSecSuccess) {
+ return false;
+ }
+
+ if (has_requirement_) {
+ if (SecRequirementCreateWithString(
+ base::mac::NSToCFCast(base::SysUTF8ToNSString(requirement_str_)),
+ kSecCSDefaultFlags,
+ requirement_.InitializeInto()) != errSecSuccess) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool MacSignatureEvaluator::PerformEvaluation(
+ ClientIncidentReport_IncidentData_BinaryIntegrityIncident* incident) {
+ DCHECK(incident->contained_file_size() == 0);
+ base::ScopedCFTypeRef<CFErrorRef> errors;
+ OSStatus err = SecStaticCodeCheckValidityWithErrors(
+ code_, kSecCSCheckAllArchitectures, requirement_,
+ errors.InitializeInto());
+ if (err == errSecSuccess)
+ return true;
+ // Add the signature of the main binary to the incident report.
+ incident->set_file_basename(path_.BaseName().value());
+ incident->set_sec_error(err);
+ // We heuristically detect if we are in a bundle or not by checking if
+ // the main executable is different from the path_.
+ base::ScopedCFTypeRef<CFDictionaryRef> info_dict;
+ if (SecCodeCopySigningInformation(code_, kSecCSDefaultFlags,
+ info_dict.InitializeInto()) ==
+ errSecSuccess) {
+ CFURLRef exec_url = base::mac::CFCastStrict<CFURLRef>(
+ CFDictionaryGetValue(info_dict, kSecCodeInfoMainExecutable));
+ if (!exec_url)
+ return false;
+
+ base::FilePath exec_path(
+ [[base::mac::CFToNSCast(exec_url) path] fileSystemRepresentation]);
+ if (exec_path != path_) {
+ ReportAlteredFiles(base::mac::CFToNSCast(exec_url), path_, incident);
+ } else {
+ // We may be examing a flat executable file, so extract any signature.
+ ExtractSignatureInfo(path_, incident->mutable_image_headers(),
+ incident->mutable_signature());
+ }
+ }
+
+ if (errors) {
+ NSDictionary* info = [base::mac::CFToNSCast(errors.get()) userInfo];
+ static const CFStringRef keys[] = {
+ kSecCFErrorResourceAltered, kSecCFErrorResourceMissing,
+ };
+ for (CFStringRef key : keys) {
+ if (id detail = [info objectForKey:base::mac::CFToNSCast(key)])
+ ReportAlteredFiles(detail, path_, incident);
+ }
+ }
+ return false;
+}
+
+} // namespace safe_browsing
diff --git a/chrome/browser/safe_browsing/signature_evaluator_mac_unittest.cc b/chrome/browser/safe_browsing/signature_evaluator_mac_unittest.cc
new file mode 100644
index 0000000..4b3fad3
--- /dev/null
+++ b/chrome/browser/safe_browsing/signature_evaluator_mac_unittest.cc
@@ -0,0 +1,328 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/safe_browsing/signature_evaluator_mac.h"
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <sys/xattr.h>
+
+#include <string>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/mac/mac_util.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "base/path_service.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/test/scoped_path_override.h"
+#include "chrome/browser/safe_browsing/incident_reporting/incident.h"
+#include "chrome/browser/safe_browsing/incident_reporting/mock_incident_receiver.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
+#include "testing/gmock/include/gmock/gmock-matchers.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::_;
+using ::testing::StrictMock;
+
+namespace safe_browsing {
+
+namespace {
+
+const char* const xattrs[] = {
+ "com.apple.cs.CodeDirectory",
+ "com.apple.cs.CodeSignature",
+ "com.apple.cs.CodeRequirements",
+ "com.apple.cs.CodeResources",
+ "com.apple.cs.CodeApplication",
+ "com.apple.cs.CodeEntitlements",
+};
+
+} // namespace
+
+class MacSignatureEvaluatorTest : public testing::Test {
+ protected:
+ void SetUp() override {
+ base::FilePath source_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &source_path));
+ testdata_path_ =
+ source_path.AppendASCII("safe_browsing").AppendASCII("mach_o");
+
+ base::FilePath dir_exe;
+ ASSERT_TRUE(PathService::Get(base::DIR_EXE, &dir_exe));
+ base::FilePath file_exe;
+ ASSERT_TRUE(PathService::Get(base::FILE_EXE, &file_exe));
+
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ }
+
+ bool SetupXattrs(const base::FilePath& path) {
+ char sentinel = 'A';
+ for (const auto& xattr : xattrs) {
+ std::vector<uint8_t> buf(10);
+ memset(&buf[0], sentinel++, buf.size());
+ if (setxattr(path.value().c_str(), xattr, &buf[0], buf.size(), 0, 0) != 0)
+ return false;
+ }
+ return true;
+ }
+
+ base::FilePath testdata_path_;
+ base::ScopedTempDir temp_dir_;
+};
+
+TEST_F(MacSignatureEvaluatorTest, RelativePathComponentTest) {
+ EXPECT_FALSE(MacSignatureEvaluator::GetRelativePathComponent(
+ base::FilePath("/foo"), base::FilePath("/bar"), nullptr));
+ EXPECT_FALSE(MacSignatureEvaluator::GetRelativePathComponent(
+ base::FilePath("/foo/bar"), base::FilePath("/bar/baz"), nullptr));
+ EXPECT_FALSE(MacSignatureEvaluator::GetRelativePathComponent(
+ base::FilePath("/foo/x"), base::FilePath("/foo/y"), nullptr));
+
+ std::string output1;
+ EXPECT_TRUE(MacSignatureEvaluator::GetRelativePathComponent(
+ base::FilePath("/foo/bar"), base::FilePath("/foo/bar/y"), &output1));
+ EXPECT_EQ(output1, "y");
+
+ std::string output2;
+ EXPECT_TRUE(MacSignatureEvaluator::GetRelativePathComponent(
+ base::FilePath("/Applications/Google Chrome.app"),
+ base::FilePath("/Applications/Google Chrome.app/Contents/MacOS/foo"),
+ &output2));
+ EXPECT_EQ(output2, "Contents/MacOS/foo");
+}
+
+TEST_F(MacSignatureEvaluatorTest, SimpleTest) {
+ // This is a simple test that checks the validity of a signed executable.
+ // There is no designated requirement: we only check the embedded signature.
+ base::FilePath path = testdata_path_.AppendASCII("signedexecutablefat");
+ safe_browsing::MacSignatureEvaluator evaluator(path);
+ ASSERT_TRUE(evaluator.Initialize());
+
+ ClientIncidentReport_IncidentData_BinaryIntegrityIncident incident;
+ EXPECT_TRUE(evaluator.PerformEvaluation(&incident));
+ EXPECT_EQ(0, incident.contained_file_size());
+}
+
+TEST_F(MacSignatureEvaluatorTest, SimpleTestWithDR) {
+ // This test checks the signer against a designated requirement description.
+ base::FilePath path = testdata_path_.AppendASCII("signedexecutablefat");
+ std::string requirement(
+ "certificate leaf[subject.CN]=\"untrusted@goat.local\"");
+ safe_browsing::MacSignatureEvaluator evaluator(path, requirement);
+ ASSERT_TRUE(evaluator.Initialize());
+
+ ClientIncidentReport_IncidentData_BinaryIntegrityIncident incident;
+ EXPECT_TRUE(evaluator.PerformEvaluation(&incident));
+ EXPECT_EQ(0, incident.contained_file_size());
+}
+
+TEST_F(MacSignatureEvaluatorTest, SimpleTestWithBadDR) {
+ // Now test with a designated requirement that does not describe the signer.
+ base::FilePath path = testdata_path_.AppendASCII("signedexecutablefat");
+ safe_browsing::MacSignatureEvaluator evaluator(path, "anchor apple");
+ ASSERT_TRUE(evaluator.Initialize());
+
+ ClientIncidentReport_IncidentData_BinaryIntegrityIncident incident;
+ EXPECT_FALSE(evaluator.PerformEvaluation(&incident));
+ EXPECT_EQ(-67050, incident.sec_error());
+ EXPECT_TRUE(incident.has_signature());
+ ASSERT_TRUE(incident.has_file_basename());
+ EXPECT_EQ("signedexecutablefat", incident.file_basename());
+}
+
+TEST_F(MacSignatureEvaluatorTest, SimpleBundleTest) {
+ // Now test a simple, validly signed bundle.
+ base::FilePath path = testdata_path_.AppendASCII("test-bundle.app");
+
+ std::string requirement(
+ "certificate leaf[subject.CN]=\"untrusted@goat.local\"");
+ safe_browsing::MacSignatureEvaluator evaluator(path, requirement);
+ ASSERT_TRUE(evaluator.Initialize());
+
+ ClientIncidentReport_IncidentData_BinaryIntegrityIncident incident;
+ EXPECT_TRUE(evaluator.PerformEvaluation(&incident));
+ EXPECT_EQ(0, incident.contained_file_size());
+}
+
+TEST_F(MacSignatureEvaluatorTest, ModifiedMainExecTest32) {
+ // Now to a test modified, signed bundle.
+ base::FilePath path = testdata_path_.AppendASCII("modified-main-exec32.app");
+
+ std::string requirement(
+ "certificate leaf[subject.CN]=\"untrusted@goat.local\"");
+ safe_browsing::MacSignatureEvaluator evaluator(path, requirement);
+ ASSERT_TRUE(evaluator.Initialize());
+
+ ClientIncidentReport_IncidentData_BinaryIntegrityIncident incident;
+ EXPECT_FALSE(evaluator.PerformEvaluation(&incident));
+ EXPECT_EQ(-67061, incident.sec_error());
+ EXPECT_EQ(path.BaseName().value(), incident.file_basename());
+ EXPECT_FALSE(incident.has_signature());
+ EXPECT_FALSE(incident.has_image_headers());
+ ASSERT_EQ(1, incident.contained_file_size());
+
+ const ClientIncidentReport_IncidentData_BinaryIntegrityIncident_ContainedFile&
+ contained_file = incident.contained_file(0);
+ EXPECT_EQ(contained_file.relative_path(), "Contents/MacOS/test-bundle");
+ EXPECT_TRUE(contained_file.has_signature());
+ EXPECT_TRUE(contained_file.has_image_headers());
+}
+
+TEST_F(MacSignatureEvaluatorTest, ModifiedMainExecTest64) {
+ // Snow Leopard does not know about the 64-bit slice so this test is
+ // irrelevant.
+ if (!base::mac::IsOSLionOrLater())
+ return;
+
+ // Now to a test modified, signed bundle.
+ base::FilePath path = testdata_path_.AppendASCII("modified-main-exec64.app");
+
+ std::string requirement(
+ "certificate leaf[subject.CN]=\"untrusted@goat.local\"");
+ safe_browsing::MacSignatureEvaluator evaluator(path, requirement);
+ ASSERT_TRUE(evaluator.Initialize());
+
+ ClientIncidentReport_IncidentData_BinaryIntegrityIncident incident;
+ EXPECT_FALSE(evaluator.PerformEvaluation(&incident));
+
+ EXPECT_EQ(-67061, incident.sec_error());
+ EXPECT_EQ(path.BaseName().value(), incident.file_basename());
+ EXPECT_FALSE(incident.has_signature());
+ EXPECT_FALSE(incident.has_image_headers());
+ ASSERT_EQ(1, incident.contained_file_size());
+
+ const ClientIncidentReport_IncidentData_BinaryIntegrityIncident_ContainedFile&
+ contained_file = incident.contained_file(0);
+ EXPECT_EQ(contained_file.relative_path(), "Contents/MacOS/test-bundle");
+ EXPECT_TRUE(contained_file.has_signature());
+ EXPECT_TRUE(contained_file.has_image_headers());
+}
+
+TEST_F(MacSignatureEvaluatorTest, ModifiedBundleAndExecTest) {
+ // Now test a modified, signed bundle with resources added and the main
+ // executable modified.
+ base::FilePath path =
+ testdata_path_.AppendASCII("modified-bundle-and-exec.app");
+
+ std::string requirement(
+ "certificate leaf[subject.CN]=\"untrusted@goat.local\"");
+ safe_browsing::MacSignatureEvaluator evaluator(path, requirement);
+ ASSERT_TRUE(evaluator.Initialize());
+
+ ClientIncidentReport_IncidentData_BinaryIntegrityIncident incident;
+ EXPECT_FALSE(evaluator.PerformEvaluation(&incident));
+ EXPECT_EQ(-67061, incident.sec_error());
+ EXPECT_FALSE(incident.has_signature());
+ EXPECT_FALSE(incident.has_image_headers());
+ EXPECT_EQ(path.BaseName().value(), incident.file_basename());
+ ASSERT_EQ(1, incident.contained_file_size());
+
+ const ClientIncidentReport_IncidentData_BinaryIntegrityIncident_ContainedFile&
+ contained_file = incident.contained_file(0);
+ EXPECT_EQ(contained_file.relative_path(), "Contents/MacOS/test-bundle");
+ EXPECT_TRUE(contained_file.has_signature());
+ EXPECT_TRUE(contained_file.has_image_headers());
+}
+
+TEST_F(MacSignatureEvaluatorTest, ModifiedBundleTest) {
+ // Now test a modified, signed bundle. This bundle has
+ // the following problems:
+ // 1) A file was added (This should not be reported)
+ // 2) libsigned64.dylib was modified
+ // 3) executable32 was modified
+
+ base::FilePath orig_path = testdata_path_.AppendASCII("modified-bundle.app");
+ base::FilePath copied_path =
+ temp_dir_.path().AppendASCII("modified-bundle.app");
+ CHECK(base::CopyDirectory(orig_path, copied_path, true));
+
+ // Setup the extended attributes, which don't persist in the git repo.
+ ASSERT_TRUE(SetupXattrs(
+ copied_path.AppendASCII("Contents/Resources/Base.lproj/MainMenu.nib")));
+
+ std::string requirement(
+ "certificate leaf[subject.CN]=\"untrusted@goat.local\"");
+ safe_browsing::MacSignatureEvaluator evaluator(copied_path, requirement);
+ ASSERT_TRUE(evaluator.Initialize());
+
+ ClientIncidentReport_IncidentData_BinaryIntegrityIncident incident;
+ EXPECT_FALSE(evaluator.PerformEvaluation(&incident));
+
+ EXPECT_TRUE(incident.has_file_basename());
+ EXPECT_EQ(copied_path.BaseName().value(), incident.file_basename());
+ EXPECT_FALSE(incident.has_signature());
+ EXPECT_FALSE(incident.has_image_headers());
+ EXPECT_EQ(-67054, incident.sec_error());
+ ASSERT_EQ(4, incident.contained_file_size());
+
+ const ClientIncidentReport_IncidentData_BinaryIntegrityIncident_ContainedFile*
+ main_exec = nullptr;
+ const ClientIncidentReport_IncidentData_BinaryIntegrityIncident_ContainedFile*
+ libsigned64 = nullptr;
+ const ClientIncidentReport_IncidentData_BinaryIntegrityIncident_ContainedFile*
+ executable32 = nullptr;
+ const ClientIncidentReport_IncidentData_BinaryIntegrityIncident_ContainedFile*
+ mainmenunib = nullptr;
+ const ClientIncidentReport_IncidentData_BinaryIntegrityIncident_ContainedFile*
+ codesign_cfg = nullptr;
+
+ for (const auto& contained_file : incident.contained_file()) {
+ if (contained_file.relative_path() == "Contents/MacOS/test-bundle") {
+ main_exec = &contained_file;
+ } else if (contained_file.relative_path() ==
+ "Contents/Frameworks/libsigned64.dylib") {
+ libsigned64 = &contained_file;
+ } else if (contained_file.relative_path() ==
+ "Contents/Resources/executable32") {
+ executable32 = &contained_file;
+ } else if (contained_file.relative_path() ==
+ "Contents/Resources/Base.lproj/MainMenu.nib") {
+ mainmenunib = &contained_file;
+ } else if (contained_file.relative_path() ==
+ "Contents/Resources/codesign.cfg") {
+ codesign_cfg = &contained_file;
+ }
+ }
+
+ ASSERT_NE(main_exec, nullptr);
+ ASSERT_NE(mainmenunib, nullptr);
+ ASSERT_NE(libsigned64, nullptr);
+ ASSERT_NE(executable32, nullptr);
+ // This is important. Do not collect information on extra files added.
+ EXPECT_EQ(codesign_cfg, nullptr);
+
+ EXPECT_TRUE(libsigned64->has_relative_path());
+ EXPECT_EQ("Contents/Frameworks/libsigned64.dylib",
+ libsigned64->relative_path());
+ EXPECT_TRUE(libsigned64->has_signature());
+
+ EXPECT_TRUE(executable32->has_relative_path());
+ EXPECT_EQ("Contents/Resources/executable32", executable32->relative_path());
+ EXPECT_TRUE(executable32->has_signature());
+ EXPECT_TRUE(executable32->has_image_headers());
+
+ EXPECT_TRUE(mainmenunib->has_relative_path());
+ EXPECT_EQ("Contents/Resources/Base.lproj/MainMenu.nib",
+ mainmenunib->relative_path());
+ EXPECT_TRUE(mainmenunib->has_signature());
+ EXPECT_EQ(6, mainmenunib->signature().xattr_size());
+ // Manually convert the global xattrs array to a vector
+ std::vector<std::string> xattrs_known;
+ for (const auto& xattr : xattrs)
+ xattrs_known.push_back(xattr);
+
+ std::vector<std::string> xattrs_seen;
+ for (const auto& xattr : mainmenunib->signature().xattr()) {
+ ASSERT_TRUE(xattr.has_key());
+ EXPECT_TRUE(xattr.has_value());
+ xattrs_seen.push_back(xattr.key());
+ }
+ EXPECT_THAT(xattrs_known, ::testing::ContainerEq(xattrs_seen));
+}
+
+} // namespace safe_browsing
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index 6c31d11..b1922db9 100644
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -2496,6 +2496,7 @@
'browser/safe_browsing/download_protection_service.h',
'browser/safe_browsing/incident_reporting/binary_integrity_analyzer.cc',
'browser/safe_browsing/incident_reporting/binary_integrity_analyzer.h',
+ 'browser/safe_browsing/incident_reporting/binary_integrity_analyzer_mac.cc',
'browser/safe_browsing/incident_reporting/binary_integrity_analyzer_win.cc',
'browser/safe_browsing/incident_reporting/binary_integrity_incident.cc',
'browser/safe_browsing/incident_reporting/binary_integrity_incident.h',
@@ -2567,6 +2568,8 @@
'browser/safe_browsing/sandboxed_dmg_analyzer_mac.h',
'browser/safe_browsing/sandboxed_zip_analyzer.cc',
'browser/safe_browsing/sandboxed_zip_analyzer.h',
+ 'browser/safe_browsing/signature_evaluator_mac.h',
+ 'browser/safe_browsing/signature_evaluator_mac.mm',
'browser/safe_browsing/two_phase_uploader.cc',
'browser/safe_browsing/two_phase_uploader.h',
'browser/safe_browsing/unverified_download_field_trial.cc',
diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi
index 0219be7..1f179ff 100644
--- a/chrome/chrome_tests_unit.gypi
+++ b/chrome/chrome_tests_unit.gypi
@@ -963,6 +963,7 @@
'browser/safe_browsing/download_feedback_service_unittest.cc',
'browser/safe_browsing/download_feedback_unittest.cc',
'browser/safe_browsing/download_protection_service_unittest.cc',
+ 'browser/safe_browsing/incident_reporting/binary_integrity_analyzer_mac_unittest.cc',
'browser/safe_browsing/incident_reporting/binary_integrity_analyzer_win_unittest.cc',
'browser/safe_browsing/incident_reporting/binary_integrity_incident_unittest.cc',
'browser/safe_browsing/incident_reporting/blacklist_load_analyzer_win_unittest.cc',
@@ -998,6 +999,7 @@
'browser/safe_browsing/safe_browsing_util_unittest.cc',
'browser/safe_browsing/sandboxed_dmg_analyzer_mac_unittest.cc',
'browser/safe_browsing/sandboxed_zip_analyzer_unittest.cc',
+ 'browser/safe_browsing/signature_evaluator_mac_unittest.cc',
'browser/safe_browsing/test_database_manager.cc',
'browser/safe_browsing/test_database_manager.h',
'browser/safe_browsing/two_phase_uploader_unittest.cc',
diff --git a/chrome/common/safe_browsing/csd.proto b/chrome/common/safe_browsing/csd.proto
index 3058e4c..db2a1f9 100644
--- a/chrome/common/safe_browsing/csd.proto
+++ b/chrome/common/safe_browsing/csd.proto
@@ -208,6 +208,16 @@ message ClientDownloadRequest {
repeated Element element = 1;
}
+ // This is an OS X only message to report extended attribute informations.
+ // Extended attributes on OS X are used for various security mechanisms,
+ // which makes them interesting to Chrome.
+ message ExtendedAttr {
+ // This is the name of the extended attribute.
+ required string key = 1;
+ // This is the value of the extended attribute.
+ optional bytes value = 2;
+ }
+
message SignatureInfo {
// All certificate chains for each of the binary's signers. Multiple chains
// may be present if the binary or any certificate has multiple signers.
@@ -225,6 +235,11 @@ message ClientDownloadRequest {
// On Mac, this is the code signature blob referenced by the
// LC_CODE_SIGNATURE load command.
repeated bytes signed_data = 3;
+
+ // On OS X, code signing data can be contained in the extended attributes of
+ // a file. As Gatekeeper respects this signature, we look for it and collect
+ // it.
+ repeated ExtendedAttr xattr = 4;
}
// This field will only be set if the binary is signed.
@@ -421,10 +436,21 @@ message ClientIncidentReport {
repeated string split_key = 3;
optional ValueState value_state = 4;
}
+
message BinaryIntegrityIncident {
optional string file_basename = 1;
optional ClientDownloadRequest.SignatureInfo signature = 2;
+ optional ClientDownloadRequest.ImageHeaders image_headers = 3;
+ optional int32 sec_error = 4;
+
+ message ContainedFile {
+ optional string relative_path = 1;
+ optional ClientDownloadRequest.SignatureInfo signature = 2;
+ optional ClientDownloadRequest.ImageHeaders image_headers = 3;
+ }
+ repeated ContainedFile contained_file = 5;
}
+
message BlacklistLoadIncident {
optional string path = 1;
optional ClientDownloadRequest.Digests digest = 2;
diff --git a/chrome/test/data/safe_browsing/mach_o/Makefile b/chrome/test/data/safe_browsing/mach_o/Makefile
index faa2fec..7b11582 100644
--- a/chrome/test/data/safe_browsing/mach_o/Makefile
+++ b/chrome/test/data/safe_browsing/mach_o/Makefile
@@ -57,3 +57,47 @@ signedexecutablefat: executablefat codesign.keychain
$(PWD)/codesign.keychain
codesign -s $(KEYCHAIN_IDENTITY) --keychain $(PWD)/codesign.keychain \
$@ --all-architectures
+
+.PHONY: test-bundle.app
+test-bundle.app: signedexecutablefat libsigned64.dylib executable32
+ ditto base-bundle.app $@
+ ditto $< $@/Contents/MacOS/test-bundle
+ ditto $(word 2,$^) $@/Contents/Frameworks/$(word 2,$^)
+ ditto $(word 3,$^) $@/Contents/Resources/$(word 3,$^)
+ security unlock-keychain -p $(KEYCHAIN_PASSWORD) \
+ $(PWD)/codesign.keychain
+ codesign -f -s $(KEYCHAIN_IDENTITY) --keychain $(PWD)/codesign.keychain \
+ $@ --all-architectures --resource-rules ResourceRules
+
+.PHONY: modified-bundle.app
+modified-bundle.app: test-bundle.app lib32.dylib executable64
+ ditto $< $@
+ touch $@/Contents/Resources/codesign.cfg
+ ditto $(word 2,$^) $@/Contents/Frameworks/libsigned64.dylib
+ ditto $(word 3,$^) $@/Contents/Resources/executable32
+ echo "foo" >> $@/Contents/Resources/Base.lproj/MainMenu.nib
+ security unlock-keychain -p $(KEYCHAIN_PASSWORD) \
+ $(PWD)/codesign.keychain
+ codesign -f -s $(KEYCHAIN_IDENTITY) --keychain $(PWD)/codesign.keychain \
+ $@/Contents/Resources/Base.lproj/MainMenu.nib
+
+.PHONY: modified-bundle-and-exec.app
+modified-bundle-and-exec.app: test-bundle.app lib32.dylib executable64
+ ditto $< $@
+ touch $@/Contents/Resources/codesign.cfg
+ ditto $(word 2,$^) $@/Contents/Frameworks/libsigned64.dylib
+ ditto $(word 3,$^) $@/Contents/Resources/executable32
+ printf '\x31' | dd bs=1 seek=8097 count=1 conv=notrunc \
+ of=$@/Contents/MacOS/test-bundle
+
+.PHONY: modified-main-exec32.app
+modified-main-exec32.app: test-bundle.app
+ ditto $< $@
+ printf '\x31' | dd bs=1 seek=8097 count=1 conv=notrunc \
+ of=$@/Contents/MacOS/test-bundle
+
+.PHONY: modified-main-exec64.app
+modified-main-exec64.app: test-bundle.app
+ ditto $< $@
+ printf '\x31' | dd bs=1 seek=24448 count=1 conv=notrunc \
+ of=$@/Contents/MacOS/test-bundle
diff --git a/chrome/test/data/safe_browsing/mach_o/ResourceRules b/chrome/test/data/safe_browsing/mach_o/ResourceRules
new file mode 100644
index 0000000..d780895
--- /dev/null
+++ b/chrome/test/data/safe_browsing/mach_o/ResourceRules
@@ -0,0 +1,99 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>rules</key>
+ <dict>
+ <key>^Resources/</key>
+ <true/>
+ <key>^Frameworks</key>
+ <true/>
+ <key>^Resources/.*\.lproj/</key>
+ <dict>
+ <key>optional</key>
+ <true/>
+ <key>weight</key>
+ <real>1000</real>
+ </dict>
+ <key>^Resources/.*\.lproj/locversion.plist$</key>
+ <dict>
+ <key>omit</key>
+ <true/>
+ <key>weight</key>
+ <real>1100</real>
+ </dict>
+ <key>^version.plist$</key>
+ <true/>
+ </dict>
+ <key>rules2</key>
+ <dict>
+ <key>.*\.dSYM($|/)</key>
+ <dict>
+ <key>weight</key>
+ <real>11</real>
+ </dict>
+ <key>^(.*/)?\.DS_Store$</key>
+ <dict>
+ <key>omit</key>
+ <true/>
+ <key>weight</key>
+ <real>2000</real>
+ </dict>
+ <key>^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/</key>
+ <dict>
+ <key>weight</key>
+ <real>10</real>
+ </dict>
+ <key>^.*</key>
+ <true/>
+ <key>^Info\.plist$</key>
+ <dict>
+ <key>omit</key>
+ <true/>
+ <key>weight</key>
+ <real>20</real>
+ </dict>
+ <key>^PkgInfo$</key>
+ <dict>
+ <key>omit</key>
+ <true/>
+ <key>weight</key>
+ <real>20</real>
+ </dict>
+ <key>^Resources/</key>
+ <dict>
+ <key>weight</key>
+ <real>20</real>
+ </dict>
+ <key>^Resources/.*\.lproj/</key>
+ <dict>
+ <key>optional</key>
+ <true/>
+ <key>weight</key>
+ <real>1000</real>
+ </dict>
+ <key>^Resources/.*\.lproj/locversion.plist$</key>
+ <dict>
+ <key>omit</key>
+ <true/>
+ <key>weight</key>
+ <real>1100</real>
+ </dict>
+ <key>^[^/]+$</key>
+ <dict>
+ <key>weight</key>
+ <real>10</real>
+ </dict>
+ <key>^embedded\.provisionprofile$</key>
+ <dict>
+ <key>weight</key>
+ <real>20</real>
+ </dict>
+ <key>^version\.plist$</key>
+ <dict>
+ <key>weight</key>
+ <real>20</real>
+ </dict>
+ </dict>
+</dict>
+</plist>
diff --git a/chrome/test/data/safe_browsing/mach_o/base-bundle.app/Contents/Info.plist b/chrome/test/data/safe_browsing/mach_o/base-bundle.app/Contents/Info.plist
new file mode 100644
index 0000000..a6082b8
--- /dev/null
+++ b/chrome/test/data/safe_browsing/mach_o/base-bundle.app/Contents/Info.plist
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>BuildMachineOSBuild</key>
+ <string>14F27</string>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>en</string>
+ <key>CFBundleExecutable</key>
+ <string>test-bundle</string>
+ <key>CFBundleIdentifier</key>
+ <string>google-test.test-bundle</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>test-bundle</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string>1</string>
+ <key>DTCompiler</key>
+ <string>com.apple.compilers.llvm.clang.1_0</string>
+ <key>DTPlatformBuild</key>
+ <string>6D2105</string>
+ <key>DTPlatformVersion</key>
+ <string>GM</string>
+ <key>DTSDKBuild</key>
+ <string>14D125</string>
+ <key>DTSDKName</key>
+ <string>macosx10.10</string>
+ <key>DTXcode</key>
+ <string>0632</string>
+ <key>DTXcodeBuild</key>
+ <string>6D2105</string>
+ <key>LSMinimumSystemVersion</key>
+ <string>10.10</string>
+ <key>NSHumanReadableCopyright</key>
+ <string>Copyright © 2015 test-bundle. All rights reserved.</string>
+ <key>NSMainNibFile</key>
+ <string>MainMenu</string>
+ <key>NSPrincipalClass</key>
+ <string>NSApplication</string>
+</dict>
+</plist>
diff --git a/chrome/test/data/safe_browsing/mach_o/base-bundle.app/Contents/PkgInfo b/chrome/test/data/safe_browsing/mach_o/base-bundle.app/Contents/PkgInfo
new file mode 100644
index 0000000..bd04210
--- /dev/null
+++ b/chrome/test/data/safe_browsing/mach_o/base-bundle.app/Contents/PkgInfo
@@ -0,0 +1 @@
+APPL???? \ No newline at end of file
diff --git a/chrome/test/data/safe_browsing/mach_o/base-bundle.app/Contents/Resources/Base.lproj/MainMenu.nib b/chrome/test/data/safe_browsing/mach_o/base-bundle.app/Contents/Resources/Base.lproj/MainMenu.nib
new file mode 100644
index 0000000..483099c
--- /dev/null
+++ b/chrome/test/data/safe_browsing/mach_o/base-bundle.app/Contents/Resources/Base.lproj/MainMenu.nib
Binary files differ
diff --git a/chrome/test/data/safe_browsing/mach_o/modified-bundle-and-exec.app/Contents/Frameworks/libsigned64.dylib b/chrome/test/data/safe_browsing/mach_o/modified-bundle-and-exec.app/Contents/Frameworks/libsigned64.dylib
new file mode 100644
index 0000000..d5a79af
--- /dev/null
+++ b/chrome/test/data/safe_browsing/mach_o/modified-bundle-and-exec.app/Contents/Frameworks/libsigned64.dylib
Binary files differ
diff --git a/chrome/test/data/safe_browsing/mach_o/modified-bundle-and-exec.app/Contents/Info.plist b/chrome/test/data/safe_browsing/mach_o/modified-bundle-and-exec.app/Contents/Info.plist
new file mode 100644
index 0000000..a6082b8
--- /dev/null
+++ b/chrome/test/data/safe_browsing/mach_o/modified-bundle-and-exec.app/Contents/Info.plist
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>BuildMachineOSBuild</key>
+ <string>14F27</string>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>en</string>
+ <key>CFBundleExecutable</key>
+ <string>test-bundle</string>
+ <key>CFBundleIdentifier</key>
+ <string>google-test.test-bundle</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>test-bundle</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string>1</string>
+ <key>DTCompiler</key>
+ <string>com.apple.compilers.llvm.clang.1_0</string>
+ <key>DTPlatformBuild</key>
+ <string>6D2105</string>
+ <key>DTPlatformVersion</key>
+ <string>GM</string>
+ <key>DTSDKBuild</key>
+ <string>14D125</string>
+ <key>DTSDKName</key>
+ <string>macosx10.10</string>
+ <key>DTXcode</key>
+ <string>0632</string>
+ <key>DTXcodeBuild</key>
+ <string>6D2105</string>
+ <key>LSMinimumSystemVersion</key>
+ <string>10.10</string>
+ <key>NSHumanReadableCopyright</key>
+ <string>Copyright © 2015 test-bundle. All rights reserved.</string>
+ <key>NSMainNibFile</key>
+ <string>MainMenu</string>
+ <key>NSPrincipalClass</key>
+ <string>NSApplication</string>
+</dict>
+</plist>
diff --git a/chrome/test/data/safe_browsing/mach_o/modified-bundle-and-exec.app/Contents/MacOS/test-bundle b/chrome/test/data/safe_browsing/mach_o/modified-bundle-and-exec.app/Contents/MacOS/test-bundle
new file mode 100644
index 0000000..48b0e84
--- /dev/null
+++ b/chrome/test/data/safe_browsing/mach_o/modified-bundle-and-exec.app/Contents/MacOS/test-bundle
Binary files differ
diff --git a/chrome/test/data/safe_browsing/mach_o/modified-bundle-and-exec.app/Contents/PkgInfo b/chrome/test/data/safe_browsing/mach_o/modified-bundle-and-exec.app/Contents/PkgInfo
new file mode 100644
index 0000000..bd04210
--- /dev/null
+++ b/chrome/test/data/safe_browsing/mach_o/modified-bundle-and-exec.app/Contents/PkgInfo
@@ -0,0 +1 @@
+APPL???? \ No newline at end of file
diff --git a/chrome/test/data/safe_browsing/mach_o/modified-bundle-and-exec.app/Contents/Resources/Base.lproj/MainMenu.nib b/chrome/test/data/safe_browsing/mach_o/modified-bundle-and-exec.app/Contents/Resources/Base.lproj/MainMenu.nib
new file mode 100644
index 0000000..483099c
--- /dev/null
+++ b/chrome/test/data/safe_browsing/mach_o/modified-bundle-and-exec.app/Contents/Resources/Base.lproj/MainMenu.nib
Binary files differ
diff --git a/chrome/test/data/safe_browsing/mach_o/modified-bundle-and-exec.app/Contents/Resources/codesign.cfg b/chrome/test/data/safe_browsing/mach_o/modified-bundle-and-exec.app/Contents/Resources/codesign.cfg
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/chrome/test/data/safe_browsing/mach_o/modified-bundle-and-exec.app/Contents/Resources/codesign.cfg
diff --git a/chrome/test/data/safe_browsing/mach_o/modified-bundle-and-exec.app/Contents/Resources/executable32 b/chrome/test/data/safe_browsing/mach_o/modified-bundle-and-exec.app/Contents/Resources/executable32
new file mode 100644
index 0000000..7191667
--- /dev/null
+++ b/chrome/test/data/safe_browsing/mach_o/modified-bundle-and-exec.app/Contents/Resources/executable32
Binary files differ
diff --git a/chrome/test/data/safe_browsing/mach_o/modified-bundle-and-exec.app/Contents/_CodeSignature/CodeResources b/chrome/test/data/safe_browsing/mach_o/modified-bundle-and-exec.app/Contents/_CodeSignature/CodeResources
new file mode 100644
index 0000000..aa06c23
--- /dev/null
+++ b/chrome/test/data/safe_browsing/mach_o/modified-bundle-and-exec.app/Contents/_CodeSignature/CodeResources
@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>files</key>
+ <dict>
+ <key>Frameworks/libsigned64.dylib</key>
+ <data>
+ eD0Asf8/OXa6v2aNFp33nh5bsGw=
+ </data>
+ <key>Resources/Base.lproj/MainMenu.nib</key>
+ <dict>
+ <key>hash</key>
+ <data>
+ 36VmRur+3AKfYrTxgwnd4cqqjvY=
+ </data>
+ <key>optional</key>
+ <true/>
+ </dict>
+ <key>Resources/executable32</key>
+ <data>
+ 8UYv2Wx4Y+rmOojWWLGJj+o5iqU=
+ </data>
+ </dict>
+ <key>files2</key>
+ <dict>
+ <key>Frameworks/libsigned64.dylib</key>
+ <data>
+ eD0Asf8/OXa6v2aNFp33nh5bsGw=
+ </data>
+ <key>Resources/Base.lproj/MainMenu.nib</key>
+ <dict>
+ <key>hash</key>
+ <data>
+ 36VmRur+3AKfYrTxgwnd4cqqjvY=
+ </data>
+ <key>optional</key>
+ <true/>
+ </dict>
+ <key>Resources/executable32</key>
+ <data>
+ 8UYv2Wx4Y+rmOojWWLGJj+o5iqU=
+ </data>
+ </dict>
+ <key>rules</key>
+ <dict>
+ <key>^Frameworks</key>
+ <true/>
+ <key>^Resources/</key>
+ <true/>
+ <key>^Resources/.*\.lproj/</key>
+ <dict>
+ <key>optional</key>
+ <true/>
+ <key>weight</key>
+ <real>1000</real>
+ </dict>
+ <key>^Resources/.*\.lproj/locversion.plist$</key>
+ <dict>
+ <key>omit</key>
+ <true/>
+ <key>weight</key>
+ <real>1100</real>
+ </dict>
+ <key>^version.plist$</key>
+ <true/>
+ </dict>
+ <key>rules2</key>
+ <dict>
+ <key>.*\.dSYM($|/)</key>
+ <dict>
+ <key>weight</key>
+ <real>11</real>
+ </dict>
+ <key>^(.*/)?\.DS_Store$</key>
+ <dict>
+ <key>omit</key>
+ <true/>
+ <key>weight</key>
+ <real>2000</real>
+ </dict>
+ <key>^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/</key>
+ <dict>
+ <key>weight</key>
+ <real>10</real>
+ </dict>
+ <key>^.*</key>
+ <true/>
+ <key>^Info\.plist$</key>
+ <dict>
+ <key>omit</key>
+ <true/>
+ <key>weight</key>
+ <real>20</real>
+ </dict>
+ <key>^PkgInfo$</key>
+ <dict>
+ <key>omit</key>
+ <true/>
+ <key>weight</key>
+ <real>20</real>
+ </dict>
+ <key>^Resources/</key>
+ <dict>
+ <key>weight</key>
+ <real>20</real>
+ </dict>
+ <key>^Resources/.*\.lproj/</key>
+ <dict>
+ <key>optional</key>
+ <true/>
+ <key>weight</key>
+ <real>1000</real>
+ </dict>
+ <key>^Resources/.*\.lproj/locversion.plist$</key>
+ <dict>
+ <key>omit</key>
+ <true/>
+ <key>weight</key>
+ <real>1100</real>
+ </dict>
+ <key>^[^/]+$</key>
+ <dict>
+ <key>weight</key>
+ <real>10</real>
+ </dict>
+ <key>^embedded\.provisionprofile$</key>
+ <dict>
+ <key>weight</key>
+ <real>20</real>
+ </dict>
+ <key>^version\.plist$</key>
+ <dict>
+ <key>weight</key>
+ <real>20</real>
+ </dict>
+ </dict>
+</dict>
+</plist>
diff --git a/chrome/test/data/safe_browsing/mach_o/modified-bundle.app/Contents/Frameworks/libsigned64.dylib b/chrome/test/data/safe_browsing/mach_o/modified-bundle.app/Contents/Frameworks/libsigned64.dylib
new file mode 100644
index 0000000..d5a79af
--- /dev/null
+++ b/chrome/test/data/safe_browsing/mach_o/modified-bundle.app/Contents/Frameworks/libsigned64.dylib
Binary files differ
diff --git a/chrome/test/data/safe_browsing/mach_o/modified-bundle.app/Contents/Info.plist b/chrome/test/data/safe_browsing/mach_o/modified-bundle.app/Contents/Info.plist
new file mode 100644
index 0000000..a6082b8
--- /dev/null
+++ b/chrome/test/data/safe_browsing/mach_o/modified-bundle.app/Contents/Info.plist
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>BuildMachineOSBuild</key>
+ <string>14F27</string>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>en</string>
+ <key>CFBundleExecutable</key>
+ <string>test-bundle</string>
+ <key>CFBundleIdentifier</key>
+ <string>google-test.test-bundle</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>test-bundle</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string>1</string>
+ <key>DTCompiler</key>
+ <string>com.apple.compilers.llvm.clang.1_0</string>
+ <key>DTPlatformBuild</key>
+ <string>6D2105</string>
+ <key>DTPlatformVersion</key>
+ <string>GM</string>
+ <key>DTSDKBuild</key>
+ <string>14D125</string>
+ <key>DTSDKName</key>
+ <string>macosx10.10</string>
+ <key>DTXcode</key>
+ <string>0632</string>
+ <key>DTXcodeBuild</key>
+ <string>6D2105</string>
+ <key>LSMinimumSystemVersion</key>
+ <string>10.10</string>
+ <key>NSHumanReadableCopyright</key>
+ <string>Copyright © 2015 test-bundle. All rights reserved.</string>
+ <key>NSMainNibFile</key>
+ <string>MainMenu</string>
+ <key>NSPrincipalClass</key>
+ <string>NSApplication</string>
+</dict>
+</plist>
diff --git a/chrome/test/data/safe_browsing/mach_o/modified-bundle.app/Contents/MacOS/test-bundle b/chrome/test/data/safe_browsing/mach_o/modified-bundle.app/Contents/MacOS/test-bundle
new file mode 100644
index 0000000..4acc277
--- /dev/null
+++ b/chrome/test/data/safe_browsing/mach_o/modified-bundle.app/Contents/MacOS/test-bundle
Binary files differ
diff --git a/chrome/test/data/safe_browsing/mach_o/modified-bundle.app/Contents/PkgInfo b/chrome/test/data/safe_browsing/mach_o/modified-bundle.app/Contents/PkgInfo
new file mode 100644
index 0000000..bd04210
--- /dev/null
+++ b/chrome/test/data/safe_browsing/mach_o/modified-bundle.app/Contents/PkgInfo
@@ -0,0 +1 @@
+APPL???? \ No newline at end of file
diff --git a/chrome/test/data/safe_browsing/mach_o/modified-bundle.app/Contents/Resources/Base.lproj/MainMenu.nib b/chrome/test/data/safe_browsing/mach_o/modified-bundle.app/Contents/Resources/Base.lproj/MainMenu.nib
new file mode 100644
index 0000000..a8a2953
--- /dev/null
+++ b/chrome/test/data/safe_browsing/mach_o/modified-bundle.app/Contents/Resources/Base.lproj/MainMenu.nib
Binary files differ
diff --git a/chrome/test/data/safe_browsing/mach_o/modified-bundle.app/Contents/Resources/codesign.cfg b/chrome/test/data/safe_browsing/mach_o/modified-bundle.app/Contents/Resources/codesign.cfg
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/chrome/test/data/safe_browsing/mach_o/modified-bundle.app/Contents/Resources/codesign.cfg
diff --git a/chrome/test/data/safe_browsing/mach_o/modified-bundle.app/Contents/Resources/executable32 b/chrome/test/data/safe_browsing/mach_o/modified-bundle.app/Contents/Resources/executable32
new file mode 100644
index 0000000..7191667
--- /dev/null
+++ b/chrome/test/data/safe_browsing/mach_o/modified-bundle.app/Contents/Resources/executable32
Binary files differ
diff --git a/chrome/test/data/safe_browsing/mach_o/modified-bundle.app/Contents/_CodeSignature/CodeResources b/chrome/test/data/safe_browsing/mach_o/modified-bundle.app/Contents/_CodeSignature/CodeResources
new file mode 100644
index 0000000..aa06c23
--- /dev/null
+++ b/chrome/test/data/safe_browsing/mach_o/modified-bundle.app/Contents/_CodeSignature/CodeResources
@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>files</key>
+ <dict>
+ <key>Frameworks/libsigned64.dylib</key>
+ <data>
+ eD0Asf8/OXa6v2aNFp33nh5bsGw=
+ </data>
+ <key>Resources/Base.lproj/MainMenu.nib</key>
+ <dict>
+ <key>hash</key>
+ <data>
+ 36VmRur+3AKfYrTxgwnd4cqqjvY=
+ </data>
+ <key>optional</key>
+ <true/>
+ </dict>
+ <key>Resources/executable32</key>
+ <data>
+ 8UYv2Wx4Y+rmOojWWLGJj+o5iqU=
+ </data>
+ </dict>
+ <key>files2</key>
+ <dict>
+ <key>Frameworks/libsigned64.dylib</key>
+ <data>
+ eD0Asf8/OXa6v2aNFp33nh5bsGw=
+ </data>
+ <key>Resources/Base.lproj/MainMenu.nib</key>
+ <dict>
+ <key>hash</key>
+ <data>
+ 36VmRur+3AKfYrTxgwnd4cqqjvY=
+ </data>
+ <key>optional</key>
+ <true/>
+ </dict>
+ <key>Resources/executable32</key>
+ <data>
+ 8UYv2Wx4Y+rmOojWWLGJj+o5iqU=
+ </data>
+ </dict>
+ <key>rules</key>
+ <dict>
+ <key>^Frameworks</key>
+ <true/>
+ <key>^Resources/</key>
+ <true/>
+ <key>^Resources/.*\.lproj/</key>
+ <dict>
+ <key>optional</key>
+ <true/>
+ <key>weight</key>
+ <real>1000</real>
+ </dict>
+ <key>^Resources/.*\.lproj/locversion.plist$</key>
+ <dict>
+ <key>omit</key>
+ <true/>
+ <key>weight</key>
+ <real>1100</real>
+ </dict>
+ <key>^version.plist$</key>
+ <true/>
+ </dict>
+ <key>rules2</key>
+ <dict>
+ <key>.*\.dSYM($|/)</key>
+ <dict>
+ <key>weight</key>
+ <real>11</real>
+ </dict>
+ <key>^(.*/)?\.DS_Store$</key>
+ <dict>
+ <key>omit</key>
+ <true/>
+ <key>weight</key>
+ <real>2000</real>
+ </dict>
+ <key>^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/</key>
+ <dict>
+ <key>weight</key>
+ <real>10</real>
+ </dict>
+ <key>^.*</key>
+ <true/>
+ <key>^Info\.plist$</key>
+ <dict>
+ <key>omit</key>
+ <true/>
+ <key>weight</key>
+ <real>20</real>
+ </dict>
+ <key>^PkgInfo$</key>
+ <dict>
+ <key>omit</key>
+ <true/>
+ <key>weight</key>
+ <real>20</real>
+ </dict>
+ <key>^Resources/</key>
+ <dict>
+ <key>weight</key>
+ <real>20</real>
+ </dict>
+ <key>^Resources/.*\.lproj/</key>
+ <dict>
+ <key>optional</key>
+ <true/>
+ <key>weight</key>
+ <real>1000</real>
+ </dict>
+ <key>^Resources/.*\.lproj/locversion.plist$</key>
+ <dict>
+ <key>omit</key>
+ <true/>
+ <key>weight</key>
+ <real>1100</real>
+ </dict>
+ <key>^[^/]+$</key>
+ <dict>
+ <key>weight</key>
+ <real>10</real>
+ </dict>
+ <key>^embedded\.provisionprofile$</key>
+ <dict>
+ <key>weight</key>
+ <real>20</real>
+ </dict>
+ <key>^version\.plist$</key>
+ <dict>
+ <key>weight</key>
+ <real>20</real>
+ </dict>
+ </dict>
+</dict>
+</plist>
diff --git a/chrome/test/data/safe_browsing/mach_o/modified-main-exec32.app/Contents/Frameworks/libsigned64.dylib b/chrome/test/data/safe_browsing/mach_o/modified-main-exec32.app/Contents/Frameworks/libsigned64.dylib
new file mode 100644
index 0000000..77b77db
--- /dev/null
+++ b/chrome/test/data/safe_browsing/mach_o/modified-main-exec32.app/Contents/Frameworks/libsigned64.dylib
Binary files differ
diff --git a/chrome/test/data/safe_browsing/mach_o/modified-main-exec32.app/Contents/Info.plist b/chrome/test/data/safe_browsing/mach_o/modified-main-exec32.app/Contents/Info.plist
new file mode 100644
index 0000000..a6082b8
--- /dev/null
+++ b/chrome/test/data/safe_browsing/mach_o/modified-main-exec32.app/Contents/Info.plist
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>BuildMachineOSBuild</key>
+ <string>14F27</string>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>en</string>
+ <key>CFBundleExecutable</key>
+ <string>test-bundle</string>
+ <key>CFBundleIdentifier</key>
+ <string>google-test.test-bundle</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>test-bundle</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string>1</string>
+ <key>DTCompiler</key>
+ <string>com.apple.compilers.llvm.clang.1_0</string>
+ <key>DTPlatformBuild</key>
+ <string>6D2105</string>
+ <key>DTPlatformVersion</key>
+ <string>GM</string>
+ <key>DTSDKBuild</key>
+ <string>14D125</string>
+ <key>DTSDKName</key>
+ <string>macosx10.10</string>
+ <key>DTXcode</key>
+ <string>0632</string>
+ <key>DTXcodeBuild</key>
+ <string>6D2105</string>
+ <key>LSMinimumSystemVersion</key>
+ <string>10.10</string>
+ <key>NSHumanReadableCopyright</key>
+ <string>Copyright © 2015 test-bundle. All rights reserved.</string>
+ <key>NSMainNibFile</key>
+ <string>MainMenu</string>
+ <key>NSPrincipalClass</key>
+ <string>NSApplication</string>
+</dict>
+</plist>
diff --git a/chrome/test/data/safe_browsing/mach_o/modified-main-exec32.app/Contents/MacOS/test-bundle b/chrome/test/data/safe_browsing/mach_o/modified-main-exec32.app/Contents/MacOS/test-bundle
new file mode 100644
index 0000000..48b0e84
--- /dev/null
+++ b/chrome/test/data/safe_browsing/mach_o/modified-main-exec32.app/Contents/MacOS/test-bundle
Binary files differ
diff --git a/chrome/test/data/safe_browsing/mach_o/modified-main-exec32.app/Contents/PkgInfo b/chrome/test/data/safe_browsing/mach_o/modified-main-exec32.app/Contents/PkgInfo
new file mode 100644
index 0000000..bd04210
--- /dev/null
+++ b/chrome/test/data/safe_browsing/mach_o/modified-main-exec32.app/Contents/PkgInfo
@@ -0,0 +1 @@
+APPL???? \ No newline at end of file
diff --git a/chrome/test/data/safe_browsing/mach_o/modified-main-exec32.app/Contents/Resources/Base.lproj/MainMenu.nib b/chrome/test/data/safe_browsing/mach_o/modified-main-exec32.app/Contents/Resources/Base.lproj/MainMenu.nib
new file mode 100644
index 0000000..483099c
--- /dev/null
+++ b/chrome/test/data/safe_browsing/mach_o/modified-main-exec32.app/Contents/Resources/Base.lproj/MainMenu.nib
Binary files differ
diff --git a/chrome/test/data/safe_browsing/mach_o/modified-main-exec32.app/Contents/Resources/executable32 b/chrome/test/data/safe_browsing/mach_o/modified-main-exec32.app/Contents/Resources/executable32
new file mode 100644
index 0000000..9337b20
--- /dev/null
+++ b/chrome/test/data/safe_browsing/mach_o/modified-main-exec32.app/Contents/Resources/executable32
Binary files differ
diff --git a/chrome/test/data/safe_browsing/mach_o/modified-main-exec32.app/Contents/_CodeSignature/CodeResources b/chrome/test/data/safe_browsing/mach_o/modified-main-exec32.app/Contents/_CodeSignature/CodeResources
new file mode 100644
index 0000000..aa06c23
--- /dev/null
+++ b/chrome/test/data/safe_browsing/mach_o/modified-main-exec32.app/Contents/_CodeSignature/CodeResources
@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>files</key>
+ <dict>
+ <key>Frameworks/libsigned64.dylib</key>
+ <data>
+ eD0Asf8/OXa6v2aNFp33nh5bsGw=
+ </data>
+ <key>Resources/Base.lproj/MainMenu.nib</key>
+ <dict>
+ <key>hash</key>
+ <data>
+ 36VmRur+3AKfYrTxgwnd4cqqjvY=
+ </data>
+ <key>optional</key>
+ <true/>
+ </dict>
+ <key>Resources/executable32</key>
+ <data>
+ 8UYv2Wx4Y+rmOojWWLGJj+o5iqU=
+ </data>
+ </dict>
+ <key>files2</key>
+ <dict>
+ <key>Frameworks/libsigned64.dylib</key>
+ <data>
+ eD0Asf8/OXa6v2aNFp33nh5bsGw=
+ </data>
+ <key>Resources/Base.lproj/MainMenu.nib</key>
+ <dict>
+ <key>hash</key>
+ <data>
+ 36VmRur+3AKfYrTxgwnd4cqqjvY=
+ </data>
+ <key>optional</key>
+ <true/>
+ </dict>
+ <key>Resources/executable32</key>
+ <data>
+ 8UYv2Wx4Y+rmOojWWLGJj+o5iqU=
+ </data>
+ </dict>
+ <key>rules</key>
+ <dict>
+ <key>^Frameworks</key>
+ <true/>
+ <key>^Resources/</key>
+ <true/>
+ <key>^Resources/.*\.lproj/</key>
+ <dict>
+ <key>optional</key>
+ <true/>
+ <key>weight</key>
+ <real>1000</real>
+ </dict>
+ <key>^Resources/.*\.lproj/locversion.plist$</key>
+ <dict>
+ <key>omit</key>
+ <true/>
+ <key>weight</key>
+ <real>1100</real>
+ </dict>
+ <key>^version.plist$</key>
+ <true/>
+ </dict>
+ <key>rules2</key>
+ <dict>
+ <key>.*\.dSYM($|/)</key>
+ <dict>
+ <key>weight</key>
+ <real>11</real>
+ </dict>
+ <key>^(.*/)?\.DS_Store$</key>
+ <dict>
+ <key>omit</key>
+ <true/>
+ <key>weight</key>
+ <real>2000</real>
+ </dict>
+ <key>^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/</key>
+ <dict>
+ <key>weight</key>
+ <real>10</real>
+ </dict>
+ <key>^.*</key>
+ <true/>
+ <key>^Info\.plist$</key>
+ <dict>
+ <key>omit</key>
+ <true/>
+ <key>weight</key>
+ <real>20</real>
+ </dict>
+ <key>^PkgInfo$</key>
+ <dict>
+ <key>omit</key>
+ <true/>
+ <key>weight</key>
+ <real>20</real>
+ </dict>
+ <key>^Resources/</key>
+ <dict>
+ <key>weight</key>
+ <real>20</real>
+ </dict>
+ <key>^Resources/.*\.lproj/</key>
+ <dict>
+ <key>optional</key>
+ <true/>
+ <key>weight</key>
+ <real>1000</real>
+ </dict>
+ <key>^Resources/.*\.lproj/locversion.plist$</key>
+ <dict>
+ <key>omit</key>
+ <true/>
+ <key>weight</key>
+ <real>1100</real>
+ </dict>
+ <key>^[^/]+$</key>
+ <dict>
+ <key>weight</key>
+ <real>10</real>
+ </dict>
+ <key>^embedded\.provisionprofile$</key>
+ <dict>
+ <key>weight</key>
+ <real>20</real>
+ </dict>
+ <key>^version\.plist$</key>
+ <dict>
+ <key>weight</key>
+ <real>20</real>
+ </dict>
+ </dict>
+</dict>
+</plist>
diff --git a/chrome/test/data/safe_browsing/mach_o/modified-main-exec64.app/Contents/Frameworks/libsigned64.dylib b/chrome/test/data/safe_browsing/mach_o/modified-main-exec64.app/Contents/Frameworks/libsigned64.dylib
new file mode 100644
index 0000000..77b77db
--- /dev/null
+++ b/chrome/test/data/safe_browsing/mach_o/modified-main-exec64.app/Contents/Frameworks/libsigned64.dylib
Binary files differ
diff --git a/chrome/test/data/safe_browsing/mach_o/modified-main-exec64.app/Contents/Info.plist b/chrome/test/data/safe_browsing/mach_o/modified-main-exec64.app/Contents/Info.plist
new file mode 100644
index 0000000..a6082b8
--- /dev/null
+++ b/chrome/test/data/safe_browsing/mach_o/modified-main-exec64.app/Contents/Info.plist
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>BuildMachineOSBuild</key>
+ <string>14F27</string>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>en</string>
+ <key>CFBundleExecutable</key>
+ <string>test-bundle</string>
+ <key>CFBundleIdentifier</key>
+ <string>google-test.test-bundle</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>test-bundle</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string>1</string>
+ <key>DTCompiler</key>
+ <string>com.apple.compilers.llvm.clang.1_0</string>
+ <key>DTPlatformBuild</key>
+ <string>6D2105</string>
+ <key>DTPlatformVersion</key>
+ <string>GM</string>
+ <key>DTSDKBuild</key>
+ <string>14D125</string>
+ <key>DTSDKName</key>
+ <string>macosx10.10</string>
+ <key>DTXcode</key>
+ <string>0632</string>
+ <key>DTXcodeBuild</key>
+ <string>6D2105</string>
+ <key>LSMinimumSystemVersion</key>
+ <string>10.10</string>
+ <key>NSHumanReadableCopyright</key>
+ <string>Copyright © 2015 test-bundle. All rights reserved.</string>
+ <key>NSMainNibFile</key>
+ <string>MainMenu</string>
+ <key>NSPrincipalClass</key>
+ <string>NSApplication</string>
+</dict>
+</plist>
diff --git a/chrome/test/data/safe_browsing/mach_o/modified-main-exec64.app/Contents/MacOS/test-bundle b/chrome/test/data/safe_browsing/mach_o/modified-main-exec64.app/Contents/MacOS/test-bundle
new file mode 100644
index 0000000..a425014
--- /dev/null
+++ b/chrome/test/data/safe_browsing/mach_o/modified-main-exec64.app/Contents/MacOS/test-bundle
Binary files differ
diff --git a/chrome/test/data/safe_browsing/mach_o/modified-main-exec64.app/Contents/PkgInfo b/chrome/test/data/safe_browsing/mach_o/modified-main-exec64.app/Contents/PkgInfo
new file mode 100644
index 0000000..bd04210
--- /dev/null
+++ b/chrome/test/data/safe_browsing/mach_o/modified-main-exec64.app/Contents/PkgInfo
@@ -0,0 +1 @@
+APPL???? \ No newline at end of file
diff --git a/chrome/test/data/safe_browsing/mach_o/modified-main-exec64.app/Contents/Resources/Base.lproj/MainMenu.nib b/chrome/test/data/safe_browsing/mach_o/modified-main-exec64.app/Contents/Resources/Base.lproj/MainMenu.nib
new file mode 100644
index 0000000..483099c
--- /dev/null
+++ b/chrome/test/data/safe_browsing/mach_o/modified-main-exec64.app/Contents/Resources/Base.lproj/MainMenu.nib
Binary files differ
diff --git a/chrome/test/data/safe_browsing/mach_o/modified-main-exec64.app/Contents/Resources/executable32 b/chrome/test/data/safe_browsing/mach_o/modified-main-exec64.app/Contents/Resources/executable32
new file mode 100644
index 0000000..9337b20
--- /dev/null
+++ b/chrome/test/data/safe_browsing/mach_o/modified-main-exec64.app/Contents/Resources/executable32
Binary files differ
diff --git a/chrome/test/data/safe_browsing/mach_o/modified-main-exec64.app/Contents/_CodeSignature/CodeResources b/chrome/test/data/safe_browsing/mach_o/modified-main-exec64.app/Contents/_CodeSignature/CodeResources
new file mode 100644
index 0000000..aa06c23
--- /dev/null
+++ b/chrome/test/data/safe_browsing/mach_o/modified-main-exec64.app/Contents/_CodeSignature/CodeResources
@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>files</key>
+ <dict>
+ <key>Frameworks/libsigned64.dylib</key>
+ <data>
+ eD0Asf8/OXa6v2aNFp33nh5bsGw=
+ </data>
+ <key>Resources/Base.lproj/MainMenu.nib</key>
+ <dict>
+ <key>hash</key>
+ <data>
+ 36VmRur+3AKfYrTxgwnd4cqqjvY=
+ </data>
+ <key>optional</key>
+ <true/>
+ </dict>
+ <key>Resources/executable32</key>
+ <data>
+ 8UYv2Wx4Y+rmOojWWLGJj+o5iqU=
+ </data>
+ </dict>
+ <key>files2</key>
+ <dict>
+ <key>Frameworks/libsigned64.dylib</key>
+ <data>
+ eD0Asf8/OXa6v2aNFp33nh5bsGw=
+ </data>
+ <key>Resources/Base.lproj/MainMenu.nib</key>
+ <dict>
+ <key>hash</key>
+ <data>
+ 36VmRur+3AKfYrTxgwnd4cqqjvY=
+ </data>
+ <key>optional</key>
+ <true/>
+ </dict>
+ <key>Resources/executable32</key>
+ <data>
+ 8UYv2Wx4Y+rmOojWWLGJj+o5iqU=
+ </data>
+ </dict>
+ <key>rules</key>
+ <dict>
+ <key>^Frameworks</key>
+ <true/>
+ <key>^Resources/</key>
+ <true/>
+ <key>^Resources/.*\.lproj/</key>
+ <dict>
+ <key>optional</key>
+ <true/>
+ <key>weight</key>
+ <real>1000</real>
+ </dict>
+ <key>^Resources/.*\.lproj/locversion.plist$</key>
+ <dict>
+ <key>omit</key>
+ <true/>
+ <key>weight</key>
+ <real>1100</real>
+ </dict>
+ <key>^version.plist$</key>
+ <true/>
+ </dict>
+ <key>rules2</key>
+ <dict>
+ <key>.*\.dSYM($|/)</key>
+ <dict>
+ <key>weight</key>
+ <real>11</real>
+ </dict>
+ <key>^(.*/)?\.DS_Store$</key>
+ <dict>
+ <key>omit</key>
+ <true/>
+ <key>weight</key>
+ <real>2000</real>
+ </dict>
+ <key>^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/</key>
+ <dict>
+ <key>weight</key>
+ <real>10</real>
+ </dict>
+ <key>^.*</key>
+ <true/>
+ <key>^Info\.plist$</key>
+ <dict>
+ <key>omit</key>
+ <true/>
+ <key>weight</key>
+ <real>20</real>
+ </dict>
+ <key>^PkgInfo$</key>
+ <dict>
+ <key>omit</key>
+ <true/>
+ <key>weight</key>
+ <real>20</real>
+ </dict>
+ <key>^Resources/</key>
+ <dict>
+ <key>weight</key>
+ <real>20</real>
+ </dict>
+ <key>^Resources/.*\.lproj/</key>
+ <dict>
+ <key>optional</key>
+ <true/>
+ <key>weight</key>
+ <real>1000</real>
+ </dict>
+ <key>^Resources/.*\.lproj/locversion.plist$</key>
+ <dict>
+ <key>omit</key>
+ <true/>
+ <key>weight</key>
+ <real>1100</real>
+ </dict>
+ <key>^[^/]+$</key>
+ <dict>
+ <key>weight</key>
+ <real>10</real>
+ </dict>
+ <key>^embedded\.provisionprofile$</key>
+ <dict>
+ <key>weight</key>
+ <real>20</real>
+ </dict>
+ <key>^version\.plist$</key>
+ <dict>
+ <key>weight</key>
+ <real>20</real>
+ </dict>
+ </dict>
+</dict>
+</plist>
diff --git a/chrome/test/data/safe_browsing/mach_o/test-bundle.app/Contents/Frameworks/libsigned64.dylib b/chrome/test/data/safe_browsing/mach_o/test-bundle.app/Contents/Frameworks/libsigned64.dylib
new file mode 100644
index 0000000..77b77db
--- /dev/null
+++ b/chrome/test/data/safe_browsing/mach_o/test-bundle.app/Contents/Frameworks/libsigned64.dylib
Binary files differ
diff --git a/chrome/test/data/safe_browsing/mach_o/test-bundle.app/Contents/Info.plist b/chrome/test/data/safe_browsing/mach_o/test-bundle.app/Contents/Info.plist
new file mode 100644
index 0000000..a6082b8
--- /dev/null
+++ b/chrome/test/data/safe_browsing/mach_o/test-bundle.app/Contents/Info.plist
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>BuildMachineOSBuild</key>
+ <string>14F27</string>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>en</string>
+ <key>CFBundleExecutable</key>
+ <string>test-bundle</string>
+ <key>CFBundleIdentifier</key>
+ <string>google-test.test-bundle</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>test-bundle</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string>1</string>
+ <key>DTCompiler</key>
+ <string>com.apple.compilers.llvm.clang.1_0</string>
+ <key>DTPlatformBuild</key>
+ <string>6D2105</string>
+ <key>DTPlatformVersion</key>
+ <string>GM</string>
+ <key>DTSDKBuild</key>
+ <string>14D125</string>
+ <key>DTSDKName</key>
+ <string>macosx10.10</string>
+ <key>DTXcode</key>
+ <string>0632</string>
+ <key>DTXcodeBuild</key>
+ <string>6D2105</string>
+ <key>LSMinimumSystemVersion</key>
+ <string>10.10</string>
+ <key>NSHumanReadableCopyright</key>
+ <string>Copyright © 2015 test-bundle. All rights reserved.</string>
+ <key>NSMainNibFile</key>
+ <string>MainMenu</string>
+ <key>NSPrincipalClass</key>
+ <string>NSApplication</string>
+</dict>
+</plist>
diff --git a/chrome/test/data/safe_browsing/mach_o/test-bundle.app/Contents/MacOS/test-bundle b/chrome/test/data/safe_browsing/mach_o/test-bundle.app/Contents/MacOS/test-bundle
new file mode 100644
index 0000000..4acc277
--- /dev/null
+++ b/chrome/test/data/safe_browsing/mach_o/test-bundle.app/Contents/MacOS/test-bundle
Binary files differ
diff --git a/chrome/test/data/safe_browsing/mach_o/test-bundle.app/Contents/PkgInfo b/chrome/test/data/safe_browsing/mach_o/test-bundle.app/Contents/PkgInfo
new file mode 100644
index 0000000..bd04210
--- /dev/null
+++ b/chrome/test/data/safe_browsing/mach_o/test-bundle.app/Contents/PkgInfo
@@ -0,0 +1 @@
+APPL???? \ No newline at end of file
diff --git a/chrome/test/data/safe_browsing/mach_o/test-bundle.app/Contents/Resources/Base.lproj/MainMenu.nib b/chrome/test/data/safe_browsing/mach_o/test-bundle.app/Contents/Resources/Base.lproj/MainMenu.nib
new file mode 100644
index 0000000..483099c
--- /dev/null
+++ b/chrome/test/data/safe_browsing/mach_o/test-bundle.app/Contents/Resources/Base.lproj/MainMenu.nib
Binary files differ
diff --git a/chrome/test/data/safe_browsing/mach_o/test-bundle.app/Contents/Resources/executable32 b/chrome/test/data/safe_browsing/mach_o/test-bundle.app/Contents/Resources/executable32
new file mode 100644
index 0000000..9337b20
--- /dev/null
+++ b/chrome/test/data/safe_browsing/mach_o/test-bundle.app/Contents/Resources/executable32
Binary files differ
diff --git a/chrome/test/data/safe_browsing/mach_o/test-bundle.app/Contents/_CodeSignature/CodeResources b/chrome/test/data/safe_browsing/mach_o/test-bundle.app/Contents/_CodeSignature/CodeResources
new file mode 100644
index 0000000..aa06c23
--- /dev/null
+++ b/chrome/test/data/safe_browsing/mach_o/test-bundle.app/Contents/_CodeSignature/CodeResources
@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>files</key>
+ <dict>
+ <key>Frameworks/libsigned64.dylib</key>
+ <data>
+ eD0Asf8/OXa6v2aNFp33nh5bsGw=
+ </data>
+ <key>Resources/Base.lproj/MainMenu.nib</key>
+ <dict>
+ <key>hash</key>
+ <data>
+ 36VmRur+3AKfYrTxgwnd4cqqjvY=
+ </data>
+ <key>optional</key>
+ <true/>
+ </dict>
+ <key>Resources/executable32</key>
+ <data>
+ 8UYv2Wx4Y+rmOojWWLGJj+o5iqU=
+ </data>
+ </dict>
+ <key>files2</key>
+ <dict>
+ <key>Frameworks/libsigned64.dylib</key>
+ <data>
+ eD0Asf8/OXa6v2aNFp33nh5bsGw=
+ </data>
+ <key>Resources/Base.lproj/MainMenu.nib</key>
+ <dict>
+ <key>hash</key>
+ <data>
+ 36VmRur+3AKfYrTxgwnd4cqqjvY=
+ </data>
+ <key>optional</key>
+ <true/>
+ </dict>
+ <key>Resources/executable32</key>
+ <data>
+ 8UYv2Wx4Y+rmOojWWLGJj+o5iqU=
+ </data>
+ </dict>
+ <key>rules</key>
+ <dict>
+ <key>^Frameworks</key>
+ <true/>
+ <key>^Resources/</key>
+ <true/>
+ <key>^Resources/.*\.lproj/</key>
+ <dict>
+ <key>optional</key>
+ <true/>
+ <key>weight</key>
+ <real>1000</real>
+ </dict>
+ <key>^Resources/.*\.lproj/locversion.plist$</key>
+ <dict>
+ <key>omit</key>
+ <true/>
+ <key>weight</key>
+ <real>1100</real>
+ </dict>
+ <key>^version.plist$</key>
+ <true/>
+ </dict>
+ <key>rules2</key>
+ <dict>
+ <key>.*\.dSYM($|/)</key>
+ <dict>
+ <key>weight</key>
+ <real>11</real>
+ </dict>
+ <key>^(.*/)?\.DS_Store$</key>
+ <dict>
+ <key>omit</key>
+ <true/>
+ <key>weight</key>
+ <real>2000</real>
+ </dict>
+ <key>^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/</key>
+ <dict>
+ <key>weight</key>
+ <real>10</real>
+ </dict>
+ <key>^.*</key>
+ <true/>
+ <key>^Info\.plist$</key>
+ <dict>
+ <key>omit</key>
+ <true/>
+ <key>weight</key>
+ <real>20</real>
+ </dict>
+ <key>^PkgInfo$</key>
+ <dict>
+ <key>omit</key>
+ <true/>
+ <key>weight</key>
+ <real>20</real>
+ </dict>
+ <key>^Resources/</key>
+ <dict>
+ <key>weight</key>
+ <real>20</real>
+ </dict>
+ <key>^Resources/.*\.lproj/</key>
+ <dict>
+ <key>optional</key>
+ <true/>
+ <key>weight</key>
+ <real>1000</real>
+ </dict>
+ <key>^Resources/.*\.lproj/locversion.plist$</key>
+ <dict>
+ <key>omit</key>
+ <true/>
+ <key>weight</key>
+ <real>1100</real>
+ </dict>
+ <key>^[^/]+$</key>
+ <dict>
+ <key>weight</key>
+ <real>10</real>
+ </dict>
+ <key>^embedded\.provisionprofile$</key>
+ <dict>
+ <key>weight</key>
+ <real>20</real>
+ </dict>
+ <key>^version\.plist$</key>
+ <dict>
+ <key>weight</key>
+ <real>20</real>
+ </dict>
+ </dict>
+</dict>
+</plist>