1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
|
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/policy/messaging_layer/upload/configuration_file_controller.h"
#include <algorithm>
#include <cstdint>
#include <limits>
#include <optional>
#include <string_view>
#include <vector>
#include "base/containers/span.h"
#include "base/feature_list.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_functions.h"
#include "base/numerics/byte_conversions.h"
#include "base/strings/string_view_util.h"
#include "chrome/browser/policy/messaging_layer/upload/record_upload_request_builder.h"
#include "components/reporting/encryption/primitives.h"
#include "components/reporting/encryption/verification.h"
#include "components/reporting/proto/synced/record.pb.h"
#include "components/reporting/util/status.h"
#include "components/version_info/version_info.h"
namespace reporting {
namespace {
// Checks if two lists contain the same destinations.
bool DestinationListsEqual(ListOfBlockedDestinations list_a,
ListOfBlockedDestinations list_b) {
if (list_a.destinations_size() != list_b.destinations_size()) {
return false;
}
return std::equal(list_a.destinations().begin(), list_a.destinations().end(),
list_b.destinations().begin());
}
} // namespace
// Only used in testing. Feature for testing the configuration file in our
// automated tests. Not exposed on the UI.
BASE_FEATURE(kReportingConfigurationFileTestSignature,
"ReportingConfigurationFileTestSignature",
base::FEATURE_DISABLED_BY_DEFAULT);
ConfigurationFileController::~ConfigurationFileController() = default;
// The default constructor used for this class takes as a parameter a callback
// that sends information to missive after being verified by this class if
// needed. It also creates the signature verifier that will be used to verify
// the signature of the configuration file using the well-known prod key stored
// in Chrome.
ConfigurationFileController::ConfigurationFileController(
UploadClient::UpdateConfigInMissiveCallback update_config_in_missive_cb)
: ConfigurationFileController(std::move(update_config_in_missive_cb),
ListOfBlockedDestinations(),
version_info::GetMajorVersionNumberAsInt()) {}
int32_t ConfigurationFileController::HandleConfigurationFile(
reporting::ConfigFile config_file) {
// Sanity check to verify that the experiment is enabled.
if (!base::FeatureList::IsEnabled(kShouldRequestConfigurationFile)) {
// Returns -1 since this is not an actual error with the file provided.
return kFeatureDisabled;
}
// Check if we got the same version that we were using already.
// This might happen if the server returns the configuration file two times
// with the same version because of two requests made close to each other, we
// just ignore them if that's the case and return the current version. We also
// just ignore the config file if the version is negative.
if (config_file.version() == current_config_file_version_ ||
config_file.version() < 0) {
return current_config_file_version_;
}
// Verify signature.
const auto signature_verification_status = VerifySignature(config_file);
if (!signature_verification_status.ok()) {
base::UmaHistogramEnumeration(
"Browser.ERP.ConfigFileSignatureVerificationError",
signature_verification_status.code(), error::Code::MAX_VALUE);
return kConfigurationFileCorrupted;
}
// Check if we should send the list of blocked destinations to missive.
if (HandleBlockedEventConfigs(config_file.blocked_event_configs())) {
update_config_in_missive_cb_.Run(destinations_list_);
}
// If we reached here it means that we just got a new version of the config
// file from the server so we update the version being sent on the payload and
// the one stored by this class.
current_config_file_version_ = config_file.version();
return current_config_file_version_;
}
Status ConfigurationFileController::VerifySignature(ConfigFile config_file) {
// We don't check the signature in our tests. This flag is only used inside
// the tast tests.
if (base::FeatureList::IsEnabled(kReportingConfigurationFileTestSignature)) {
return Status::StatusOK();
}
// Sanity checks, this should never fail.
if (!config_file.has_version()) {
return Status{error::INVALID_ARGUMENT,
"Missing version information for config file"};
}
if (!config_file.has_config_file_signature()) {
return Status{error::INVALID_ARGUMENT,
"Missing signature information for config file"};
}
// Verify the value signed on the server using the big-endian representation
// of the configuration file version.
return verifier_.Verify(
base::as_string_view(base::U32ToBigEndian(config_file.version())),
config_file.config_file_signature());
}
// static
std::unique_ptr<ConfigurationFileController>
ConfigurationFileController::CreateForTesting(
UploadClient::UpdateConfigInMissiveCallback update_config_in_missive_cb,
ListOfBlockedDestinations destinations_list,
int os_version) {
return base::WrapUnique(new ConfigurationFileController(
std::move(update_config_in_missive_cb), std::move(destinations_list),
os_version));
}
ConfigurationFileController::ConfigurationFileController(
UploadClient::UpdateConfigInMissiveCallback update_config_in_missive_cb,
ListOfBlockedDestinations destinations_list,
int os_version)
: update_config_in_missive_cb_(std::move(update_config_in_missive_cb)),
verifier_(SignatureVerifier(SignatureVerifier::VerificationKey())),
destinations_list_(std::move(destinations_list)),
current_os_version_(os_version) {}
bool ConfigurationFileController::HandleBlockedEventConfigs(
google::protobuf::RepeatedPtrField<EventConfig> blocked_event_configs) {
// If the incoming list is empty and the stored list is also empty
// we return false to signal to the parent function that it should not
// send the list to missive.
if (blocked_event_configs.empty() &&
destinations_list_.destinations().empty()) {
return false;
}
// If the configuration fetched from the server is empty and the stored list
// is not empty we return true to signal to the parent function that it
// should update the list in missive to an empty one.
if (blocked_event_configs.empty() &&
!destinations_list_.destinations().empty()) {
destinations_list_ = ListOfBlockedDestinations();
return true;
}
ListOfBlockedDestinations current_list;
// Check all the destinations, if they have version information
// we check whether it should be blocked or not.
for (const auto& current_event : blocked_event_configs) {
// If there is no minimum/maximum release version specified we add it
// directly since it should be blocked on all versions.
if (!current_event.has_minimum_release_version()) {
current_list.add_destinations(current_event.destination());
continue;
}
// Check if it should be blocked for the current version or not.
std::optional<int32_t> maximum_version;
maximum_version =
current_event.has_maximum_release_version()
? std::optional<int32_t>(current_event.maximum_release_version())
: std::nullopt;
if (ShouldBeBlocked(current_event.minimum_release_version(),
maximum_version)) {
current_list.add_destinations(current_event.destination());
}
}
// Compare to see if the destinations list that we have right now is the same
// as the one that is incoming from the server, if not then we swap the lists
// and we notify the parent function that it should send the new list to
// missive.
if (DestinationListsEqual(current_list, destinations_list_)) {
return false;
}
destinations_list_ = std::move(current_list);
return true;
}
bool ConfigurationFileController::ShouldBeBlocked(
int32_t minimum_version,
std::optional<int32_t> maximum_version) const {
if (maximum_version.has_value()) {
return current_os_version_ >= minimum_version &&
current_os_version_ <= maximum_version.value();
}
return current_os_version_ >= minimum_version;
}
} // namespace reporting
|