| // Copyright 2021 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/component_updater/pki_metadata_component_installer.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/base64.h" |
| #include "base/bind.h" |
| #include "base/feature_list.h" |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/logging.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/task/post_task.h" |
| #include "base/task/thread_pool.h" |
| #include "base/threading/scoped_blocking_call.h" |
| #include "base/time/time.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/network_service_instance.h" |
| #include "services/network/public/cpp/network_service_buildflags.h" |
| |
| #if BUILDFLAG(IS_CT_SUPPORTED) |
| #include "components/certificate_transparency/certificate_transparency.pb.h" |
| #include "components/certificate_transparency/certificate_transparency_config.pb.h" |
| #include "components/certificate_transparency/ct_features.h" |
| #include "services/network/public/mojom/ct_log_info.mojom.h" |
| #include "services/network/public/mojom/network_service.mojom.h" |
| #endif |
| |
| using component_updater::ComponentUpdateService; |
| |
| namespace { |
| |
| const char kGoogleOperatorName[] = "Google"; |
| |
| // The SHA256 of the SubjectPublicKeyInfo used to sign the extension. |
| // The extension id is: efniojlnjndmcbiieegkicadnoecjjef |
| const uint8_t kPKIMetadataPublicKeySHA256[32] = { |
| 0x45, 0xd8, 0xe9, 0xbd, 0x9d, 0x3c, 0x21, 0x88, 0x44, 0x6a, 0x82, |
| 0x03, 0xde, 0x42, 0x99, 0x45, 0x66, 0x25, 0xfe, 0xb3, 0xd1, 0xf8, |
| 0x11, 0x65, 0xb4, 0x6f, 0xd3, 0x1b, 0x21, 0x89, 0xbe, 0x9c}; |
| |
| const base::FilePath::CharType kCTConfigProtoFileName[] = |
| FILE_PATH_LITERAL("ct_config.pb"); |
| |
| std::string LoadCTBinaryProtoFromDisk(const base::FilePath& pb_path) { |
| std::string result; |
| if (pb_path.empty()) |
| return result; |
| |
| base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, |
| base::BlockingType::WILL_BLOCK); |
| |
| if (!base::ReadFileToString(pb_path.Append(kCTConfigProtoFileName), |
| &result)) { |
| result.clear(); |
| } |
| return result; |
| } |
| |
| } // namespace |
| |
| namespace component_updater { |
| |
| PKIMetadataComponentInstallerPolicy::PKIMetadataComponentInstallerPolicy() = |
| default; |
| |
| PKIMetadataComponentInstallerPolicy::~PKIMetadataComponentInstallerPolicy() = |
| default; |
| |
| bool PKIMetadataComponentInstallerPolicy:: |
| SupportsGroupPolicyEnabledComponentUpdates() const { |
| return false; |
| } |
| |
| bool PKIMetadataComponentInstallerPolicy::RequiresNetworkEncryption() const { |
| return false; |
| } |
| |
| update_client::CrxInstaller::Result |
| PKIMetadataComponentInstallerPolicy::OnCustomInstall( |
| const base::DictionaryValue& /* manifest */, |
| const base::FilePath& /* install_dir */) { |
| return update_client::CrxInstaller::Result(0); // Nothing custom here. |
| } |
| |
| void PKIMetadataComponentInstallerPolicy::OnCustomUninstall() {} |
| |
| void PKIMetadataComponentInstallerPolicy::ComponentReady( |
| const base::Version& version, |
| const base::FilePath& install_dir, |
| std::unique_ptr<base::DictionaryValue> /* manifest */) { |
| base::ThreadPool::PostTaskAndReplyWithResult( |
| FROM_HERE, {base::TaskPriority::BEST_EFFORT, base::MayBlock()}, |
| base::BindOnce(&LoadCTBinaryProtoFromDisk, install_dir), |
| base::BindOnce( |
| &PKIMetadataComponentInstallerPolicy::UpdateNetworkServiceOnUI, |
| base::Unretained(this))); |
| } |
| |
| // Called during startup and installation before ComponentReady(). |
| bool PKIMetadataComponentInstallerPolicy::VerifyInstallation( |
| const base::DictionaryValue& /* manifest */, |
| const base::FilePath& install_dir) const { |
| if (!base::PathExists(install_dir)) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| base::FilePath PKIMetadataComponentInstallerPolicy::GetRelativeInstallDir() |
| const { |
| return base::FilePath(FILE_PATH_LITERAL("PKIMetadata")); |
| } |
| |
| void PKIMetadataComponentInstallerPolicy::GetHash( |
| std::vector<uint8_t>* hash) const { |
| hash->assign(std::begin(kPKIMetadataPublicKeySHA256), |
| std::end(kPKIMetadataPublicKeySHA256)); |
| } |
| |
| std::string PKIMetadataComponentInstallerPolicy::GetName() const { |
| return "PKI Metadata"; |
| } |
| |
| update_client::InstallerAttributes |
| PKIMetadataComponentInstallerPolicy::GetInstallerAttributes() const { |
| return update_client::InstallerAttributes(); |
| } |
| |
| void PKIMetadataComponentInstallerPolicy::UpdateNetworkServiceOnUI( |
| const std::string& ct_config_bytes) { |
| #if BUILDFLAG(IS_CT_SUPPORTED) |
| auto proto = |
| std::make_unique<chrome_browser_certificate_transparency::CTConfig>(); |
| if (!proto->ParseFromString(ct_config_bytes)) { |
| return; |
| } |
| |
| network::mojom::NetworkService* network_service = |
| content::GetNetworkService(); |
| |
| if (proto->disable_ct_enforcement()) { |
| network_service->SetCtEnforcementEnabled(false); |
| return; |
| } |
| |
| std::vector<network::mojom::CTLogInfoPtr> log_list_mojo; |
| |
| // The log list shipped via component updater is a single message of CTLogList |
| // type, as defined in |
| // components/certificate_transparency/certificate_transparency.proto, the |
| // included logs are of the CTLog type, but include only the information |
| // required by Chrome to enforce its CT policy. Non Chrome used fields are |
| // left unset. |
| for (auto log : proto->log_list().logs()) { |
| std::string decoded_key; |
| if (!base::Base64Decode(log.key(), &decoded_key)) { |
| continue; |
| } |
| network::mojom::CTLogInfoPtr log_ptr = network::mojom::CTLogInfo::New(); |
| log_ptr->name = log.description(); |
| log_ptr->public_key = decoded_key; |
| // Operator history is ordered in inverse chronological order, so the 0th |
| // element will be the current operator. |
| if (!log.operator_history().empty() && |
| log.operator_history().Get(0).name() == kGoogleOperatorName) { |
| log_ptr->operated_by_google = true; |
| } |
| // State history is ordered in inverse chronological order, so the 0th |
| // element will be the current state. |
| if (!log.state().empty()) { |
| const auto& state = log.state().Get(0); |
| if (state.current_state() == |
| chrome_browser_certificate_transparency::CTLog_CurrentState_RETIRED) { |
| // If the log was RETIRED, record the timestamp at which it was. |
| // Note: RETIRED is a terminal state for the log, so other states do not |
| // need to be checked, because once RETIRED, the state will never |
| // change. |
| base::TimeDelta retired_since = |
| base::TimeDelta::FromSeconds( |
| log.state()[0].state_start().seconds()) + |
| base::TimeDelta::FromNanoseconds( |
| log.state()[0].state_start().nanos()); |
| log_ptr->disqualified_at = retired_since; |
| } |
| } |
| log_list_mojo.push_back(std::move(log_ptr)); |
| } |
| |
| base::Time update_time = |
| base::Time::UnixEpoch() + |
| base::TimeDelta::FromSeconds(proto->log_list().timestamp().seconds()) + |
| base::TimeDelta::FromNanoseconds(proto->log_list().timestamp().nanos()); |
| network_service->UpdateCtLogList(std::move(log_list_mojo), update_time); |
| #endif // BUILDFLAG(IS_CT_SUPPORTED) |
| } |
| |
| void MaybeRegisterPKIMetadataComponent(ComponentUpdateService* cus) { |
| // Currently the component is only used for the CT log list, so we no-op if CT |
| // is not supported. |
| #if BUILDFLAG(IS_CT_SUPPORTED) |
| if (!base::FeatureList::IsEnabled( |
| certificate_transparency::features:: |
| kCertificateTransparencyComponentUpdater)) { |
| return; |
| } |
| auto installer = base::MakeRefCounted<ComponentInstaller>( |
| std::make_unique<PKIMetadataComponentInstallerPolicy>()); |
| installer->Register(cus, base::OnceClosure()); |
| #endif // BUILDFLAG(IS_CT_SUPPORTED) |
| } |
| |
| } // namespace component_updater |