1 // Copyright 2024 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "components/metrics/structured/key_data_prefs_delegate.h"
6
7 #include <memory>
8 #include <string_view>
9
10 #include "base/logging.h"
11 #include "base/memory/raw_ptr.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "base/test/metrics/histogram_tester.h"
14 #include "base/test/task_environment.h"
15 #include "base/time/time.h"
16 #include "components/metrics/structured/histogram_util.h"
17 #include "components/metrics/structured/lib/histogram_util.h"
18 #include "components/metrics/structured/lib/key_data.h"
19 #include "components/metrics/structured/lib/key_util.h"
20 #include "components/metrics/structured/lib/proto/key.pb.h"
21 #include "components/metrics/structured/structured_metrics_validator.h"
22 #include "components/prefs/pref_registry_simple.h"
23 #include "components/prefs/scoped_user_pref_update.h"
24 #include "components/prefs/testing_pref_service.h"
25 #include "testing/gtest/include/gtest/gtest.h"
26
27 namespace metrics::structured {
28
29 namespace {
30 constexpr char kTestPrefName[] = "TestPref";
31
32 // 32 byte long test key, matching the size of a real key.
33 constexpr char kKey[] = "abcdefghijklmnopqrstuvwxyzabcdef";
34
35 // These project, event, and metric names are used for testing.
36 // - project: TestProjectOne
37 // - event: TestEventOne
38 // - metric: TestMetricOne
39 // - metric: TestMetricTwo
40 // - project: TestProjectTwo
41
42 // The name hash of "TestProjectOne".
43 constexpr uint64_t kProjectOneHash = 16881314472396226433ull;
44 // The name hash of "TestProjectTwo".
45 constexpr uint64_t kProjectTwoHash = 5876808001962504629ull;
46
47 // The name hash of "TestMetricOne".
48 constexpr uint64_t kMetricOneHash = 637929385654885975ull;
49 // The name hash of "TestMetricTwo".
50 constexpr uint64_t kMetricTwoHash = 14083999144141567134ull;
51
52 // The hex-encoded frst 8 bytes of SHA256(kKey), ie. the user ID for key kKey.
53 constexpr char kUserId[] = "2070DF23E0D95759";
54
55 // Test values and their hashes. Hashes are the first 8 bytes of:
56 // HMAC_SHA256(concat(hex(kMetricNHash), kValueN), kKey)
57 constexpr char kValueOne[] = "value one";
58 constexpr char kValueTwo[] = "value two";
59 constexpr char kValueOneHash[] = "805B8790DC69B773";
60 constexpr char kValueTwoHash[] = "87CEF12FB15E0B3A";
61
62 constexpr base::TimeDelta kKeyRotationPeriod = base::Days(90);
63
HashToHex(const uint64_t hash)64 std::string HashToHex(const uint64_t hash) {
65 return base::HexEncode(&hash, sizeof(uint64_t));
66 }
67 } // namespace
68
69 class KeyDataPrefsDelegateTest : public testing::Test {
70 public:
SetUp()71 void SetUp() override {
72 prefs_.registry()->RegisterDictionaryPref(kTestPrefName);
73 // Move the mock date forward from day 0, because KeyDataFileDelegate
74 // assumes that day 0 is a bug.
75 task_environment_.AdvanceClock(base::Days(1000));
76 }
77
CreateKeyData()78 void CreateKeyData() {
79 auto delegate =
80 std::make_unique<KeyDataPrefsDelegate>(&prefs_, kTestPrefName);
81 delegate_ = delegate.get();
82 key_data_ = std::make_unique<KeyData>(std::move(delegate));
83 }
84
ResetKeyData()85 void ResetKeyData() {
86 delegate_ = nullptr;
87 key_data_.reset();
88 }
89
90 // Read the key directly from the prefs.
GetKey(const uint64_t project_name_hash)91 KeyProto GetKey(const uint64_t project_name_hash) {
92 auto* validators = validator::Validators::Get();
93
94 base::StringPiece project_name =
95 validators->GetProjectName(project_name_hash).value();
96
97 const base::Value::Dict& keys_dict = prefs_.GetDict(kTestPrefName);
98
99 const base::Value::Dict* value = keys_dict.FindDict(project_name);
100
101 std::optional<KeyProto> key = util::CreateKeyProtoFromValue(*value);
102
103 return std::move(key).value();
104 }
105
Today()106 int Today() { return (base::Time::Now() - base::Time::UnixEpoch()).InDays(); }
107
108 // Write a KeyDataProto to prefs with a single key described by the
109 // arguments.
SetupKey(const uint64_t project_name_hash,const std::string & key,const int last_rotation,const base::TimeDelta rotation_period)110 bool SetupKey(const uint64_t project_name_hash,
111 const std::string& key,
112 const int last_rotation,
113 const base::TimeDelta rotation_period) {
114 // It's a test logic error for the key data to exist when calling SetupKey,
115 // because it will desync the in-memory proto from the underlying storage.
116 if (key_data_) {
117 return false;
118 }
119
120 KeyProto key_proto;
121 key_proto.set_key(key);
122 key_proto.set_last_rotation(last_rotation);
123 key_proto.set_rotation_period(rotation_period.InDays());
124
125 ScopedDictPrefUpdate pref_updater(&prefs_, kTestPrefName);
126
127 base::Value::Dict& dict = pref_updater.Get();
128 const validator::Validators* validators = validator::Validators::Get();
129 auto project_name = validators->GetProjectName(project_name_hash);
130
131 auto value = util::CreateValueFromKeyProto(key_proto);
132
133 dict.Set(*project_name, std::move(value));
134 return true;
135 }
136
ExpectKeyValidation(const int valid,const int created,const int rotated)137 void ExpectKeyValidation(const int valid,
138 const int created,
139 const int rotated) {
140 static constexpr char kHistogram[] =
141 "UMA.StructuredMetrics.KeyValidationState";
142 histogram_tester_.ExpectBucketCount(kHistogram, KeyValidationState::kValid,
143 valid);
144 histogram_tester_.ExpectBucketCount(kHistogram,
145 KeyValidationState::kCreated, created);
146 histogram_tester_.ExpectBucketCount(kHistogram,
147 KeyValidationState::kRotated, rotated);
148 }
149
150 protected:
151 base::test::TaskEnvironment task_environment_{
152 base::test::TaskEnvironment::MainThreadType::UI,
153 base::test::TaskEnvironment::ThreadPoolExecutionMode::QUEUED,
154 base::test::TaskEnvironment::TimeSource::MOCK_TIME};
155 TestingPrefServiceSimple prefs_;
156 base::HistogramTester histogram_tester_;
157
158 std::unique_ptr<KeyData> key_data_;
159 raw_ptr<KeyDataPrefsDelegate> delegate_;
160 };
161
162 // If there is no key store file present, check that new keys are generated for
163 // each project, and those keys are of the right length and different from each
164 // other.
TEST_F(KeyDataPrefsDelegateTest,GeneratesKeysForProjects)165 TEST_F(KeyDataPrefsDelegateTest, GeneratesKeysForProjects) {
166 // Make key data and use two keys, in order to generate them.
167 CreateKeyData();
168 key_data_->Id(kProjectOneHash, kKeyRotationPeriod.InDays());
169 key_data_->Id(kProjectTwoHash, kKeyRotationPeriod.InDays());
170
171 const std::string key_one = GetKey(kProjectOneHash).key();
172 const std::string key_two = GetKey(kProjectTwoHash).key();
173
174 EXPECT_EQ(key_one.size(), 32ul);
175 EXPECT_EQ(key_two.size(), 32ul);
176 EXPECT_NE(key_one, key_two);
177
178 ExpectKeyValidation(/*valid=*/0, /*created=*/2, /*rotated=*/0);
179 }
180
181 // If there is an existing key store file, check that its keys are not replaced.
TEST_F(KeyDataPrefsDelegateTest,ReuseExistingKeys)182 TEST_F(KeyDataPrefsDelegateTest, ReuseExistingKeys) {
183 // Create a file with one key.
184 CreateKeyData();
185 const uint64_t id_one =
186 key_data_->Id(kProjectOneHash, kKeyRotationPeriod.InDays());
187 ExpectKeyValidation(/*valid=*/0, /*created=*/1, /*rotated=*/0);
188 const std::string key_one = GetKey(kProjectOneHash).key();
189
190 // Reset the in-memory state, leave the on-disk state intact.
191 ResetKeyData();
192
193 // Open the file again and check we use the same key.
194 CreateKeyData();
195 const uint64_t id_two =
196 key_data_->Id(kProjectOneHash, kKeyRotationPeriod.InDays());
197 ExpectKeyValidation(/*valid=*/1, /*created=*/1, /*rotated=*/0);
198 const std::string key_two = GetKey(kProjectOneHash).key();
199
200 EXPECT_EQ(id_one, id_two);
201 EXPECT_EQ(key_one, key_two);
202 }
203
204 // Check that different events have different hashes for the same metric and
205 // value.
TEST_F(KeyDataPrefsDelegateTest,DifferentEventsDifferentHashes)206 TEST_F(KeyDataPrefsDelegateTest, DifferentEventsDifferentHashes) {
207 CreateKeyData();
208 EXPECT_NE(key_data_->HmacMetric(kProjectOneHash, kMetricOneHash, "value",
209 kKeyRotationPeriod.InDays()),
210 key_data_->HmacMetric(kProjectTwoHash, kMetricOneHash, "value",
211 kKeyRotationPeriod.InDays()));
212 }
213
214 // Check that an event has different hashes for different metrics with the same
215 // value.
TEST_F(KeyDataPrefsDelegateTest,DifferentMetricsDifferentHashes)216 TEST_F(KeyDataPrefsDelegateTest, DifferentMetricsDifferentHashes) {
217 CreateKeyData();
218 EXPECT_NE(key_data_->HmacMetric(kProjectOneHash, kMetricOneHash, "value",
219 kKeyRotationPeriod.InDays()),
220 key_data_->HmacMetric(kProjectOneHash, kMetricTwoHash, "value",
221 kKeyRotationPeriod.InDays()));
222 }
223
224 // Check that an event has different hashes for different values of the same
225 // metric.
TEST_F(KeyDataPrefsDelegateTest,DifferentValuesDifferentHashes)226 TEST_F(KeyDataPrefsDelegateTest, DifferentValuesDifferentHashes) {
227 CreateKeyData();
228 EXPECT_NE(key_data_->HmacMetric(kProjectOneHash, kMetricOneHash, "first",
229 kKeyRotationPeriod.InDays()),
230 key_data_->HmacMetric(kProjectOneHash, kMetricOneHash, "second",
231 kKeyRotationPeriod.InDays()));
232 }
233
234 // Ensure that KeyDataFileDelegate::UserId is the expected value of SHA256(key).
TEST_F(KeyDataPrefsDelegateTest,CheckUserIDs)235 TEST_F(KeyDataPrefsDelegateTest, CheckUserIDs) {
236 ASSERT_TRUE(SetupKey(kProjectOneHash, kKey, Today(), kKeyRotationPeriod));
237
238 CreateKeyData();
239 EXPECT_EQ(
240 HashToHex(key_data_->Id(kProjectOneHash, kKeyRotationPeriod.InDays())),
241 kUserId);
242 EXPECT_NE(
243 HashToHex(key_data_->Id(kProjectTwoHash, kKeyRotationPeriod.InDays())),
244 kUserId);
245 }
246
247 // Ensure that KeyDataFileDelegate::Hash returns expected values for a known
248 // key / and value.
TEST_F(KeyDataPrefsDelegateTest,CheckHashes)249 TEST_F(KeyDataPrefsDelegateTest, CheckHashes) {
250 ASSERT_TRUE(SetupKey(kProjectOneHash, kKey, Today(), kKeyRotationPeriod));
251
252 CreateKeyData();
253 EXPECT_EQ(
254 HashToHex(key_data_->HmacMetric(kProjectOneHash, kMetricOneHash,
255 kValueOne, kKeyRotationPeriod.InDays())),
256 kValueOneHash);
257 EXPECT_EQ(
258 HashToHex(key_data_->HmacMetric(kProjectOneHash, kMetricTwoHash,
259 kValueTwo, kKeyRotationPeriod.InDays())),
260 kValueTwoHash);
261 }
262
263 //// Check that keys for a event are correctly rotated after a given rotation
264 //// period.
TEST_F(KeyDataPrefsDelegateTest,KeysRotated)265 TEST_F(KeyDataPrefsDelegateTest, KeysRotated) {
266 const int start_day = Today();
267 ASSERT_TRUE(SetupKey(kProjectOneHash, kKey, start_day, kKeyRotationPeriod));
268
269 CreateKeyData();
270 const uint64_t first_id =
271 key_data_->Id(kProjectOneHash, kKeyRotationPeriod.InDays());
272 EXPECT_EQ(key_data_->LastKeyRotation(kProjectOneHash), start_day);
273 ExpectKeyValidation(/*valid=*/1, /*created=*/0, /*rotated=*/0);
274
275 {
276 // Advancing by |kKeyRotationPeriod|-1 days, the key should not be rotated.
277 task_environment_.AdvanceClock(base::Days(kKeyRotationPeriod.InDays() - 1));
278 EXPECT_EQ(key_data_->Id(kProjectOneHash, kKeyRotationPeriod.InDays()),
279 first_id);
280 EXPECT_EQ(key_data_->LastKeyRotation(kProjectOneHash), start_day);
281
282 ASSERT_EQ(GetKey(kProjectOneHash).last_rotation(), start_day);
283 ExpectKeyValidation(/*valid=*/2, /*created=*/0, /*rotated=*/0);
284 }
285
286 {
287 // Advancing by another |key_rotation_period|+1 days, the key should be
288 // rotated and the last rotation day should be incremented by
289 // |key_rotation_period|.
290 task_environment_.AdvanceClock(base::Days(kKeyRotationPeriod.InDays() + 1));
291 EXPECT_NE(key_data_->Id(kProjectOneHash, kKeyRotationPeriod.InDays()),
292 first_id);
293
294 int expected_last_key_rotation =
295 start_day + 2 * kKeyRotationPeriod.InDays();
296 EXPECT_EQ(GetKey(kProjectOneHash).last_rotation(),
297 expected_last_key_rotation);
298 EXPECT_EQ(key_data_->LastKeyRotation(kProjectOneHash),
299 expected_last_key_rotation);
300 ExpectKeyValidation(/*valid=*/2, /*created=*/0, /*rotated=*/1);
301
302 ASSERT_EQ(GetKey(kProjectOneHash).rotation_period(),
303 kKeyRotationPeriod.InDays());
304 }
305
306 {
307 // Advancing by |2* kKeyRotationPeriod| days, the last rotation day should
308 // now 4 periods of |kKeyRotationPeriod| days ahead.
309 task_environment_.AdvanceClock(base::Days(kKeyRotationPeriod.InDays() * 2));
310 key_data_->Id(kProjectOneHash, kKeyRotationPeriod.InDays());
311
312 int expected_last_key_rotation =
313 start_day + 4 * kKeyRotationPeriod.InDays();
314 EXPECT_EQ(GetKey(kProjectOneHash).last_rotation(),
315 expected_last_key_rotation);
316 EXPECT_EQ(key_data_->LastKeyRotation(kProjectOneHash),
317 expected_last_key_rotation);
318 ExpectKeyValidation(/*valid=*/2, /*created=*/0, /*rotated=*/2);
319 }
320 }
321
322 //// Check that keys with updated rotations are correctly rotated.
TEST_F(KeyDataPrefsDelegateTest,KeysWithUpdatedRotations)323 TEST_F(KeyDataPrefsDelegateTest, KeysWithUpdatedRotations) {
324 base::TimeDelta first_key_rotation_period = base::Days(60);
325
326 const int start_day = Today();
327 ASSERT_TRUE(
328 SetupKey(kProjectOneHash, kKey, start_day, first_key_rotation_period));
329
330 CreateKeyData();
331 const uint64_t first_id =
332 key_data_->Id(kProjectOneHash, first_key_rotation_period.InDays());
333 EXPECT_EQ(key_data_->LastKeyRotation(kProjectOneHash), start_day);
334 ExpectKeyValidation(/*valid=*/1, /*created=*/0, /*rotated=*/0);
335
336 // Advance days by |new_key_rotation_period| + 1. This should fall within
337 // the rotation of the |new_key_rotation_period| but outside
338 // |first_key_rotation_period|.
339 int new_key_rotation_period = 50;
340 task_environment_.AdvanceClock(base::Days(new_key_rotation_period + 1));
341 const uint64_t second_id =
342 key_data_->Id(kProjectOneHash, new_key_rotation_period);
343 EXPECT_NE(first_id, second_id);
344
345 // Key should have been rotated with new_key_rotation_period.
346 int expected_last_key_rotation = start_day + new_key_rotation_period;
347 EXPECT_EQ(GetKey(kProjectOneHash).last_rotation(),
348 expected_last_key_rotation);
349 EXPECT_EQ(key_data_->LastKeyRotation(kProjectOneHash),
350 expected_last_key_rotation);
351 ExpectKeyValidation(/*valid=*/1, /*created=*/0, /*rotated=*/1);
352 }
353
TEST_F(KeyDataPrefsDelegateTest,Purge)354 TEST_F(KeyDataPrefsDelegateTest, Purge) {
355 const int start_day = Today();
356 ASSERT_TRUE(SetupKey(kProjectOneHash, kKey, start_day, kKeyRotationPeriod));
357
358 CreateKeyData();
359 EXPECT_EQ(delegate_->proto_.keys().size(), 1ul);
360
361 key_data_->Purge();
362 EXPECT_EQ(delegate_->proto_.keys().size(), 0ul);
363
364 const base::Value::Dict& keys_dict = prefs_.GetDict(kTestPrefName);
365 EXPECT_EQ(keys_dict.size(), 0ul);
366 }
367
368 } // namespace metrics::structured
369