910e62b5创建于 1月15日历史提交
// 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_CONTENT_HASH_H_
#define EXTENSIONS_BROWSER_CONTENT_VERIFIER_CONTENT_HASH_H_

#include <set>

#include "base/files/file_path.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr_exclusion.h"
#include "base/memory/ref_counted.h"
#include "base/version.h"
#include "extensions/browser/computed_hashes.h"
#include "extensions/browser/content_verifier/content_verifier_delegate.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 "mojo/public/cpp/bindings/pending_remote.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> {
 public:
  // The combined (network or http response) error code while fetching.
  using FetchErrorCode = int;

  // Holds key to identify an extension for content verification, parameters to
  // fetch verified_contents.json and other supplementary info.
  struct FetchKey {
    // Extension info.
    ExtensionId extension_id;
    base::FilePath extension_root;
    base::Version extension_version;

    // Fetch parameters.
    mojo::PendingRemote<network::mojom::URLLoaderFactory>
        url_loader_factory_remote;
    GURL fetch_url;

    // The key used to validate verified_contents.json.
    // TODO(367764863) Rewrite to base::raw_span.
    RAW_PTR_EXCLUSION ContentVerifierKey verifier_key;

    FetchKey(const ExtensionId& extension_id,
             const base::FilePath& extension_root,
             const base::Version& extension_version,
             mojo::PendingRemote<network::mojom::URLLoaderFactory>
                 url_loader_factory_remote,
             const GURL& fetch_url,
             ContentVerifierKey verifier_key);

    FetchKey(const FetchKey&) = delete;
    FetchKey& operator=(const FetchKey&) = delete;

    ~FetchKey();

    FetchKey(FetchKey&& other);
    FetchKey& operator=(FetchKey&& other);
  };

  // Result of checking tree hash root (typically calculated from block hashes
  // in computed_hashes.json) against signed hash from verified_contents.json.
  enum class TreeHashVerificationResult {
    // Hash is correct.
    SUCCESS,

    // There is no such file in verified_contents.json.
    NO_ENTRY,

    // Hash does not match the one from verified_contents.json.
    HASH_MISMATCH
  };

  ContentHash(const ContentHash&) = delete;
  ContentHash& operator=(const ContentHash&) = delete;

  // 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(FetchKey key,
                     ContentVerifierDelegate::VerifierSourceType source_type,
                     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 https://crbug.com/819832 is fixed.
  void ForceBuildComputedHashes(const IsCancelledCallback& is_cancelled,
                                CreatedCallback created_callback);

  // Returns the result of comparing tree hash `root` for the `relative_path` to
  // verified_contens.json data.
  TreeHashVerificationResult VerifyTreeHashRoot(
      const base::FilePath& relative_path,
      const std::string* root) const;

  const ComputedHashes& computed_hashes() const;

  // Returns loading status of hashes.
  ComputedHashes::Status computed_hashes_status() const {
    return computed_hashes_status_;
  }

  // Returns whether or not computed_hashes.json (and, if needed,
  // verified_contents.json too) was read correctly and is ready to use.
  bool succeeded() const {
    return computed_hashes_status_ == ComputedHashes::Status::SUCCESS;
  }

  // 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 ExtensionId& extension_id() const { return extension_id_; }
  const base::FilePath& extension_root() const { return extension_root_; }
  const base::Version& extension_version() const { return extension_version_; }

  // Returns whether or not computed_hashes.json re-creation might be required
  // for `this` to succeed.
  // TODO(lazyboy): Remove this once https://crbug.com/819832 is fixed.
  bool might_require_computed_hashes_force_creation() const {
    return !succeeded() && verified_contents_ != nullptr &&
           !did_attempt_creating_computed_hashes_;
  }

  static std::string ComputeTreeHashForContent(const std::string& contents,
                                               int block_size);

 private:
  friend class base::RefCountedThreadSafe<ContentHash>;

  using GetVerifiedContentsCallback = base::OnceCallback<void(
      FetchKey key,
      std::unique_ptr<VerifiedContents> verified_contents,
      bool did_attempt_fetch,
      FetchErrorCode fetch_error)>;

  ContentHash(const ExtensionId& id,
              const base::FilePath& root,
              const base::Version& extension_version,
              ContentVerifierDelegate::VerifierSourceType source_type,
              std::unique_ptr<const VerifiedContents> verified_contents);
  ~ContentHash();

  // Step 1/2: verified_contents.json.
  static void GetVerifiedContents(
      FetchKey key,
      ContentVerifierDelegate::VerifierSourceType source_type,
      const IsCancelledCallback& is_cancelled,
      GetVerifiedContentsCallback);
  static void FetchVerifiedContents(FetchKey key,
                                    const IsCancelledCallback& is_cancelled,
                                    GetVerifiedContentsCallback callback);
  static std::unique_ptr<VerifiedContents> StoreAndRetrieveVerifiedContents(
      std::unique_ptr<std::string> fetched_contents,
      const FetchKey& key);
  static void DidFetchVerifiedContents(
      GetVerifiedContentsCallback callback,
      FetchKey key,
      std::unique_ptr<std::string> fetched_contents,
      FetchErrorCode fetch_error);

  // Step 2/2: computed_hashes.json.
  static void GetComputedHashes(
      ContentVerifierDelegate::VerifierSourceType source_type,
      const IsCancelledCallback& is_cancelled,
      CreatedCallback created_callback,
      FetchKey key,
      std::unique_ptr<VerifiedContents> verified_contents,
      bool did_attempt_fetch,
      FetchErrorCode fetch_error);

  static void DispatchFetchFailure(
      const ExtensionId& extension_id,
      const base::FilePath& extension_root,
      const base::Version& extension_version,
      ContentVerifierDelegate::VerifierSourceType source_type,
      CreatedCallback created_callback,
      const IsCancelledCallback& is_cancelled,
      FetchErrorCode fetch_error);

  static void RecordFetchResult(bool success, FetchErrorCode fetch_error);

  // 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 did_fetch_verified_contents,
                           bool force_build,
                           const IsCancelledCallback& is_cancelled);

  // Helper callback for ComputedHashes::Create. Checks whether we want the hash
  // of the given resource be in computes_hashes.json or not.
  bool ShouldComputeHashesForResource(const base::FilePath& relative_unix_path);

  // If needed (`source_type_` is SIGNED_HASHES) checks each hash from
  // `computed_hashes` against data from verified_contenst.json and returns list
  // of mismatches. If not needed, just returns empty list.
  std::set<base::FilePath> GetMismatchedComputedHashes(
      ComputedHashes::Data* computed_hashes);

  bool has_verified_contents() const { return verified_contents_ != nullptr; }

  const ExtensionId extension_id_;
  const base::FilePath extension_root_;
  // The version of the extension associated with the hash.
  // Used for comparing against the version from `ContentVerifyJob` when
  // starting the job.
  const base::Version extension_version_;
  ContentVerifierDelegate::VerifierSourceType source_type_;

  ComputedHashes::Status computed_hashes_status_ =
      ComputedHashes::Status::UNKNOWN;

  bool did_attempt_creating_computed_hashes_ = false;

  // TODO(lazyboy): Avoid dynamic allocations here, `this` already supports
  // move.
  std::unique_ptr<const VerifiedContents> verified_contents_;
  std::unique_ptr<const ComputedHashes> 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

#endif  // EXTENSIONS_BROWSER_CONTENT_VERIFIER_CONTENT_HASH_H_