blob: e411fd35ad492428b7a1e132f8319400b179e938 [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef EXTENSIONS_BROWSER_CONTENT_VERIFIER_TEST_UTILS_H_
#define EXTENSIONS_BROWSER_CONTENT_VERIFIER_TEST_UTILS_H_
#include <list>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include "base/files/file_path.h"
#include "base/run_loop.h"
#include "base/task/sequenced_task_runner.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/test/test_utils.h"
#include "crypto/rsa_private_key.h"
#include "extensions/browser/content_hash_reader.h"
#include "extensions/browser/content_verifier/content_hash.h"
#include "extensions/browser/content_verifier/content_verifier.h"
#include "extensions/browser/content_verifier/content_verifier_delegate.h"
#include "extensions/browser/content_verifier/content_verify_job.h"
#include "extensions/common/extension_id.h"
#include "extensions/test/test_extension_dir.h"
namespace extensions {
class Extension;
// Test class to observe *a particular* extension resource's ContentVerifyJob
// lifetime. Provides a way to wait for a job to finish and return
// the job's result.
class TestContentVerifySingleJobObserver {
public:
TestContentVerifySingleJobObserver(const ExtensionId& extension_id,
const base::FilePath& relative_path);
~TestContentVerifySingleJobObserver();
TestContentVerifySingleJobObserver(
const TestContentVerifySingleJobObserver&) = delete;
TestContentVerifySingleJobObserver& operator=(
const TestContentVerifySingleJobObserver&) = delete;
// Waits for a ContentVerifyJob to finish and returns job's status.
[[nodiscard]] ContentVerifyJob::FailureReason WaitForJobFinished();
// Waits for ContentVerifyJob to finish the attempt to read content hashes.
ContentHashReader::InitStatus WaitForOnHashesReady();
private:
class ObserverClient : public ContentVerifyJob::TestObserver {
public:
ObserverClient(const ExtensionId& extension_id,
const base::FilePath& relative_path);
ObserverClient(const ObserverClient&) = delete;
ObserverClient& operator=(const ObserverClient&) = delete;
// ContentVerifyJob::TestObserver:
void JobStarted(const ExtensionId& extension_id,
const base::FilePath& relative_path) override {}
void JobFinished(const ExtensionId& extension_id,
const base::FilePath& relative_path,
ContentVerifyJob::FailureReason reason) override;
void OnHashesReady(const ExtensionId& extension_id,
const base::FilePath& relative_path,
const ContentHashReader& hash_reader) override;
// Passed methods from ContentVerifySingleJobObserver:
[[nodiscard]] ContentVerifyJob::FailureReason WaitForJobFinished();
ContentHashReader::InitStatus WaitForOnHashesReady();
private:
~ObserverClient() override;
void OnHashesReadyOnCreationThread(
const ExtensionId& extension_id,
const base::FilePath& relative_path,
ContentHashReader::InitStatus content_hash_status);
content::BrowserThread::ID creation_thread_;
base::RunLoop job_finished_run_loop_;
base::RunLoop on_hashes_ready_run_loop_;
ExtensionId extension_id_;
base::FilePath relative_path_;
std::optional<ContentVerifyJob::FailureReason> failure_reason_;
bool seen_on_hashes_ready_ = false;
ContentHashReader::InitStatus hashes_status_;
};
scoped_refptr<ObserverClient> client_;
};
// Test class to observe expected set of ContentVerifyJobs.
class TestContentVerifyJobObserver {
public:
TestContentVerifyJobObserver();
~TestContentVerifyJobObserver();
TestContentVerifyJobObserver(const TestContentVerifyJobObserver&) = delete;
TestContentVerifyJobObserver& operator=(const TestContentVerifyJobObserver&) =
delete;
enum class Result { SUCCESS, FAILURE };
// Call this to add an expected job result.
void ExpectJobResult(const ExtensionId& extension_id,
const base::FilePath& relative_path,
Result expected_result);
// Wait to see expected jobs. Returns true when we've seen all expected jobs
// finish, or false if there was an error or timeout.
bool WaitForExpectedJobs();
private:
class ObserverClient : public ContentVerifyJob::TestObserver {
public:
ObserverClient();
ObserverClient(const ObserverClient&) = delete;
ObserverClient& operator=(const ObserverClient&) = delete;
// ContentVerifyJob::TestObserver:
void JobStarted(const ExtensionId& extension_id,
const base::FilePath& relative_path) override {}
void JobFinished(const ExtensionId& extension_id,
const base::FilePath& relative_path,
ContentVerifyJob::FailureReason failure_reason) override;
void OnHashesReady(const ExtensionId& extension_id,
const base::FilePath& relative_path,
const ContentHashReader& hash_reader) override {}
// Passed methods from TestContentVerifyJobObserver:
void ExpectJobResult(const ExtensionId& extension_id,
const base::FilePath& relative_path,
Result expected_result);
bool WaitForExpectedJobs();
private:
struct ExpectedResult {
public:
ExtensionId extension_id;
base::FilePath path;
Result result;
ExpectedResult(const ExtensionId& extension_id,
const base::FilePath& path,
Result result)
: extension_id(extension_id), path(path), result(result) {}
};
~ObserverClient() override;
std::list<ExpectedResult> expectations_;
content::BrowserThread::ID creation_thread_;
// Accessed on `creation_thread_`.
base::OnceClosure job_quit_closure_;
};
scoped_refptr<ObserverClient> client_;
};
// An extensions/ implementation of ContentVerifierDelegate for using in tests.
// Provides mock versions of content verification mode, keys and fetch url.
class MockContentVerifierDelegate : public ContentVerifierDelegate {
public:
MockContentVerifierDelegate();
MockContentVerifierDelegate(const MockContentVerifierDelegate&) = delete;
MockContentVerifierDelegate& operator=(const MockContentVerifierDelegate&) =
delete;
~MockContentVerifierDelegate() override;
// ContentVerifierDelegate:
VerifierSourceType GetVerifierSourceType(const Extension& extension) override;
ContentVerifierKey GetPublicKey() override;
GURL GetSignatureFetchUrl(const ExtensionId& extension_id,
const base::Version& version) override;
std::set<base::FilePath> GetBrowserImagePaths(
const extensions::Extension* extension) override;
void VerifyFailed(const ExtensionId& extension_id,
ContentVerifyJob::FailureReason reason) override;
void Shutdown() override;
// Modifier.
void SetVerifierSourceType(VerifierSourceType type);
void SetVerifierKey(std::vector<uint8_t> key);
private:
VerifierSourceType verifier_source_type_ = VerifierSourceType::SIGNED_HASHES;
std::vector<uint8_t> verifier_key_;
};
// Observes ContentVerifier::OnFetchComplete of a particular extension.
class VerifierObserver : public ContentVerifier::TestObserver {
public:
VerifierObserver();
VerifierObserver(const VerifierObserver&) = delete;
VerifierObserver& operator=(const VerifierObserver&) = delete;
virtual ~VerifierObserver();
const std::set<base::FilePath>& hash_mismatch_unix_paths() {
DCHECK(content_hash_);
return content_hash_->hash_mismatch_unix_paths();
}
bool did_hash_mismatch() const { return did_hash_mismatch_; }
// Ensures that `extension_id` has seen OnFetchComplete, waits for it to
// complete if it hasn't already.
void EnsureFetchCompleted(const ExtensionId& extension_id);
// ContentVerifier::TestObserver
void OnFetchComplete(const scoped_refptr<const ContentHash>& content_hash,
bool did_hash_mismatch) override;
private:
std::set<ExtensionId> completed_fetches_;
ExtensionId id_to_wait_for_;
scoped_refptr<const ContentHash> content_hash_;
bool did_hash_mismatch_ = true;
// Created and accessed on `creation_thread_`.
scoped_refptr<content::MessageLoopRunner> loop_runner_;
content::BrowserThread::ID creation_thread_;
base::WeakPtrFactory<VerifierObserver> weak_ptr_factory_{this};
};
// Used to hold the result of a callback from the ContentHash creation.
struct ContentHashResult {
ContentHashResult(const ExtensionId& extension_id,
bool success,
bool was_cancelled,
const std::set<base::FilePath> mismatch_paths);
~ContentHashResult();
ExtensionId extension_id;
bool success;
bool was_cancelled;
std::set<base::FilePath> mismatch_paths;
};
// Allows waiting for the callback from a ContentHash, returning the
// data that was passed to that callback.
class ContentHashWaiter {
public:
ContentHashWaiter();
ContentHashWaiter(const ContentHashWaiter&) = delete;
ContentHashWaiter& operator=(const ContentHashWaiter&) = delete;
~ContentHashWaiter();
std::unique_ptr<ContentHashResult> CreateAndWaitForCallback(
ContentHash::FetchKey key,
ContentVerifierDelegate::VerifierSourceType source_type);
private:
void CreatedCallback(scoped_refptr<ContentHash> content_hash,
bool was_cancelled);
void CreateContentHash(
ContentHash::FetchKey key,
ContentVerifierDelegate::VerifierSourceType source_type);
scoped_refptr<base::SequencedTaskRunner> reply_task_runner_;
base::RunLoop run_loop_;
std::unique_ptr<ContentHashResult> result_;
};
namespace content_verifier_test_utils {
// Helper class to create directory with extension files, including signed
// hashes for content verification.
class TestExtensionBuilder {
public:
TestExtensionBuilder();
explicit TestExtensionBuilder(const ExtensionId& extension_id);
~TestExtensionBuilder();
TestExtensionBuilder(const TestExtensionBuilder&) = delete;
TestExtensionBuilder& operator=(const TestExtensionBuilder&) = delete;
// Accept parameters by values since we'll store them.
void AddResource(base::FilePath::StringType relative_path,
std::string contents);
void WriteManifest();
// Accept parameters by values since we'll store them.
void WriteResource(base::FilePath::StringType relative_path,
std::string contents);
void WriteComputedHashes();
std::string CreateVerifiedContents() const;
void WriteVerifiedContents();
std::vector<uint8_t> GetTestContentVerifierPublicKey() const;
base::FilePath extension_path() const {
return extension_dir_.UnpackedPath();
}
const ExtensionId& extension_id() const { return extension_id_; }
private:
struct ExtensionResource {
ExtensionResource(base::FilePath relative_path, std::string contents)
: relative_path(std::move(relative_path)),
contents(std::move(contents)) {}
base::FilePath relative_path;
std::string contents;
};
std::unique_ptr<base::Value> CreateVerifiedContentsPayload() const;
std::unique_ptr<crypto::RSAPrivateKey> test_content_verifier_key_;
ExtensionId extension_id_;
std::vector<ExtensionResource> extension_resources_;
TestExtensionDir extension_dir_;
};
// Unzips the extension source from `extension_zip` into `unzip_dir`
// directory and loads it. Returns the resulting Extension object.
// `destination` points to the path where the extension was extracted.
//
// TODO(lazyboy): Move this function to a generic file.
scoped_refptr<Extension> UnzipToDirAndLoadExtension(
const base::FilePath& extension_zip,
const base::FilePath& unzip_dir);
} // namespace content_verifier_test_utils
} // namespace extensions
#endif // EXTENSIONS_BROWSER_CONTENT_VERIFIER_TEST_UTILS_H_