blob: 2cc33b709cc927644876cf0f61053361e8cbcbe8 [file] [log] [blame]
// Copyright 2018 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 <set>
#include "base/callback_helpers.h"
#include "base/files/file_path.h"
#include "base/version.h"
#include "extensions/browser/computed_hashes.h"
#include "extensions/browser/content_verifier/content_verifier_key.h"
#include "extensions/browser/verified_contents.h"
#include "extensions/common/constants.h"
#include "extensions/common/extension_id.h"
#include "services/network/public/mojom/url_loader_factory.mojom.h"
#include "url/gurl.h"
namespace extensions {
// Represents content verification hashes for an extension.
// Instances can be created using Create() factory method on sequences with
// blocking IO access. If hash retrieval succeeds then ContentHash::succeeded()
// will return true and
// a. ContentHash::verified_contents() will return structured representation of
// verified_contents.json
// b. ContentHash::computed_hashes() will return structured representation of
// computed_hashes.json.
// If verified_contents.json was missing on disk (e.g. because of disk
// corruption or such), this class will fetch the file from network. After
// fetching the class will parse/validate this data as needed, including
// calculating expected hashes for each block of each file within an extension.
// (These unsigned leaf node block level hashes will always be checked at time
// of use use to make sure they match the signed treehash root hash).
// computed_hashes.json is computed over the files in an extension's directory.
// If computed_hashes.json was required to be written to disk and
// it was successful, ContentHash::hash_mismatch_unix_paths() will return all
// FilePaths from the extension directory that had content verification
// mismatch.
// Clients of this class can cancel the disk write operation of
// computed_hashes.json while it is ongoing. This is because it can potentially
// take long time. This cancellation can be performed through |is_cancelled|.
class ContentHash : public base::RefCountedThreadSafe<ContentHash> {
// Key to identify an extension.
struct ExtensionKey {
ExtensionId extension_id;
base::FilePath extension_root;
base::Version extension_version;
// The key used to validate verified_contents.json.
ContentVerifierKey verifier_key;
ExtensionKey(const ExtensionId& extension_id,
const base::FilePath& extension_root,
const base::Version& extension_version,
ContentVerifierKey verifier_key);
ExtensionKey(const ExtensionKey& other);
ExtensionKey& operator=(const ExtensionKey& other);
// Parameters to fetch verified_contents.json.
struct FetchParams {
network::mojom::URLLoaderFactoryPtrInfo url_loader_factory_ptr_info,
const GURL& fetch_url);
FetchParams& operator=(FetchParams&&);
network::mojom::URLLoaderFactoryPtrInfo url_loader_factory_ptr_info;
GURL fetch_url;
using IsCancelledCallback = base::RepeatingCallback<bool(void)>;
// Factory:
// Returns ContentHash through |created_callback|, the returned values are:
// - |hash| The content hash. This will never be nullptr, but
// verified_contents or computed_hashes may be empty if something fails.
// - |was_cancelled| Indicates whether or not the request was cancelled
// through |is_cancelled|, while it was being processed.
using CreatedCallback =
base::OnceCallback<void(scoped_refptr<ContentHash> hash,
bool was_cancelled)>;
static void Create(const ExtensionKey& key,
FetchParams fetch_params,
const IsCancelledCallback& is_cancelled,
CreatedCallback created_callback);
// Forces creation of computed_hashes.json. Must be called with after
// |verified_contents| has been successfully set.
// TODO(lazyboy): Remove this once is fixed.
void ForceBuildComputedHashes(const IsCancelledCallback& is_cancelled,
CreatedCallback created_callback);
const VerifiedContents& verified_contents() const;
const ComputedHashes::Reader& computed_hashes() const;
bool has_verified_contents() const {
return status_ >= Status::kHasVerifiedContents;
bool succeeded() const { return status_ >= Status::kSucceeded; }
// If ContentHash creation writes computed_hashes.json, then this returns the
// FilePaths whose content hash didn't match expected hashes.
const std::set<base::FilePath>& hash_mismatch_unix_paths() const {
return hash_mismatch_unix_paths_;
const ExtensionKey extension_key() const { return key_; }
// Returns whether or not computed_hashes.json re-creation might be required
// for |this| to succeed.
// TODO(lazyboy): Remove this once is fixed.
bool might_require_computed_hashes_force_creation() const {
return !succeeded() && has_verified_contents() &&
friend class base::RefCountedThreadSafe<ContentHash>;
enum class Status {
// Retrieving hashes failed.
// Retrieved valid verified_contents.json, but there was no
// computed_hashes.json.
// Both verified_contents.json and computed_hashes.json were read
// correctly.
ContentHash(const ExtensionKey& key,
std::unique_ptr<VerifiedContents> verified_contents,
std::unique_ptr<ComputedHashes::Reader> computed_hashes);
static void FetchVerifiedContents(const ExtensionKey& extension_key,
FetchParams fetch_params,
const IsCancelledCallback& is_cancelled,
CreatedCallback created_callback);
static void DidFetchVerifiedContents(
CreatedCallback created_callback,
const IsCancelledCallback& is_cancelled,
const ExtensionKey& key,
std::unique_ptr<std::string> fetched_contents);
static void DispatchFetchFailure(const ExtensionKey& key,
CreatedCallback created_callback,
const IsCancelledCallback& is_cancelled);
static void RecordFetchResult(bool success);
// Computes hashes for all files in |key_.extension_root|, and uses
// a ComputedHashes::Writer to write that information into |hashes_file|.
// Returns true on success.
// The verified contents file from the webstore only contains the treehash
// root hash, but for performance we want to cache the individual block level
// hashes. This function will create that cache with block-level hashes for
// each file in the extension if needed (the treehash root hash for each of
// these should equal what is in the verified contents file from the
// webstore).
bool CreateHashes(const base::FilePath& hashes_file,
const IsCancelledCallback& is_cancelled);
// Builds computed_hashes. Possibly after creating computed_hashes.json file
// if necessary.
void BuildComputedHashes(bool attempted_fetching_verified_contents,
bool force_build,
const IsCancelledCallback& is_cancelled);
ExtensionKey key_;
Status status_ = Status::kInvalid;
bool did_attempt_creating_computed_hashes_ = false;
// TODO(lazyboy): Avoid dynamic allocations here, |this| already supports
// move.
std::unique_ptr<VerifiedContents> verified_contents_;
std::unique_ptr<ComputedHashes::Reader> computed_hashes_;
// Paths that were found to have a mismatching hash.
std::set<base::FilePath> hash_mismatch_unix_paths_;
// The block size to use for hashing.
// TODO(asargent) - use the value from verified_contents.json for each
// file, instead of using a constant.
int block_size_ = extension_misc::kContentVerificationDefaultBlockSize;
} // namespace extensions