blob: 70fed8582491bdf33c4b1def5e047c46ac27571f [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "extensions/browser/content_verifier/content_hash.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "extensions/browser/computed_hashes.h"
#include "extensions/browser/content_hash_tree.h"
#include "extensions/browser/content_verifier/test_utils.h"
#include "extensions/browser/content_verifier_delegate.h"
#include "extensions/browser/extension_file_task_runner.h"
#include "extensions/browser/extensions_test.h"
#include "extensions/browser/verified_contents.h"
#include "extensions/common/constants.h"
#include "extensions/common/file_util.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "services/network/test/test_url_loader_factory.h"
namespace extensions {
class ContentHashUnittest : public ExtensionsTest {
protected:
ContentHashUnittest() = default;
std::unique_ptr<ContentHashResult> CreateContentHash(
Extension* extension,
ContentVerifierDelegate::VerifierSourceType source_type,
const std::vector<uint8_t>& content_verifier_public_key) {
mojo::PendingRemote<network::mojom::URLLoaderFactory> url_loader_factory;
network::TestURLLoaderFactory().Clone(
url_loader_factory.InitWithNewPipeAndPassReceiver());
ContentHash::FetchKey key(
extension->id(), extension->path(), extension->version(),
std::move(url_loader_factory), GURL() /* fetch_url */,
content_verifier_public_key);
return ContentHashWaiter().CreateAndWaitForCallback(std::move(key),
source_type);
}
scoped_refptr<Extension> LoadExtension(
const content_verifier_test_utils::TestExtensionBuilder& builder) {
std::string error;
scoped_refptr<Extension> extension = file_util::LoadExtension(
builder.extension_path(), builder.extension_id(),
mojom::ManifestLocation::kInternal, 0 /* flags */, &error);
if (!extension)
ADD_FAILURE() << " error:'" << error << "'";
return extension;
}
void CheckContentHashValidityWithOverrideExtensionId(
const content_verifier_test_utils::TestExtensionBuilder& builder,
const ExtensionId& override_extension_id,
bool expected_is_valid) {
// Copy the extension files to a temp directory as the verification process
// may delete the verified_contents.json file.
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
ASSERT_TRUE(base::CopyDirectory(builder.extension_path(),
temp_dir.GetPath(), /*recursive=*/true));
std::string error;
auto extension = file_util::LoadExtension(
temp_dir.GetPath().Append(builder.extension_path().BaseName()),
override_extension_id, mojom::ManifestLocation::kInternal,
0 /* flags */, &error);
ASSERT_TRUE(extension) << "Error: " << error;
auto content_hash = CreateContentHash(
extension.get(),
ContentVerifierDelegate::VerifierSourceType::SIGNED_HASHES,
builder.GetTestContentVerifierPublicKey());
ASSERT_NE(content_hash, nullptr);
EXPECT_EQ(content_hash->success, expected_is_valid);
}
};
TEST_F(ContentHashUnittest, ExtensionWithSignedHashes) {
content_verifier_test_utils::TestExtensionBuilder builder;
builder.WriteManifest();
builder.WriteResource(FILE_PATH_LITERAL("background.js"),
"console.log('Nothing special');");
builder.WriteVerifiedContents();
scoped_refptr<Extension> extension = LoadExtension(builder);
ASSERT_NE(nullptr, extension);
std::unique_ptr<ContentHashResult> result = CreateContentHash(
extension.get(),
ContentVerifierDelegate::VerifierSourceType::SIGNED_HASHES,
builder.GetTestContentVerifierPublicKey());
DCHECK(result);
EXPECT_TRUE(result->success);
}
TEST_F(ContentHashUnittest, ExtensionWithUnsignedHashes) {
content_verifier_test_utils::TestExtensionBuilder builder;
builder.WriteManifest();
builder.WriteResource(FILE_PATH_LITERAL("background.js"),
"console.log('Nothing special');");
builder.WriteComputedHashes();
scoped_refptr<Extension> extension = LoadExtension(builder);
ASSERT_NE(nullptr, extension);
std::unique_ptr<ContentHashResult> result = CreateContentHash(
extension.get(),
ContentVerifierDelegate::VerifierSourceType::UNSIGNED_HASHES,
builder.GetTestContentVerifierPublicKey());
DCHECK(result);
EXPECT_TRUE(result->success);
}
TEST_F(ContentHashUnittest, ExtensionWithoutHashes) {
content_verifier_test_utils::TestExtensionBuilder builder;
builder.WriteManifest();
builder.WriteResource(FILE_PATH_LITERAL("background.js"),
"console.log('Nothing special');");
scoped_refptr<Extension> extension = LoadExtension(builder);
ASSERT_NE(nullptr, extension);
std::unique_ptr<ContentHashResult> result = CreateContentHash(
extension.get(),
ContentVerifierDelegate::VerifierSourceType::UNSIGNED_HASHES,
builder.GetTestContentVerifierPublicKey());
DCHECK(result);
EXPECT_FALSE(result->success);
}
// Try to load an extension with the verified_contents.json of a different
// extension and verify that it fails.
TEST_F(ContentHashUnittest, SignedHashesWithIncorrectExtensionId) {
const ExtensionId signed_extension_id(32, 'a');
const ExtensionId incorrect_extension_id(32, 'b');
// Create extension with verified_contents.json for |signed_extension_id|.
content_verifier_test_utils::TestExtensionBuilder builder(
signed_extension_id);
builder.WriteManifest();
builder.WriteResource(FILE_PATH_LITERAL("background.js"),
"console.log('Nothing special');");
builder.WriteVerifiedContents();
CheckContentHashValidityWithOverrideExtensionId(builder, signed_extension_id,
/*expected_is_valid=*/true);
CheckContentHashValidityWithOverrideExtensionId(builder,
incorrect_extension_id,
/*expected_is_valid=*/false);
}
} // namespace extensions