File: configuration_file_controller.cc

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (207 lines) | stat: -rw-r--r-- 8,457 bytes parent folder | download | duplicates (3)
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