• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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     std::string_view 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   base::TimeDelta Today() {
107     return base::Time::Now() - base::Time::UnixEpoch();
108   }
109 
110   // Write a KeyDataProto to prefs with a single key described by the
111   // arguments.
SetupKey(const uint64_t project_name_hash,const std::string & key,const base::TimeDelta last_rotation,const base::TimeDelta rotation_period)112   bool SetupKey(const uint64_t project_name_hash,
113                 const std::string& key,
114                 const base::TimeDelta last_rotation,
115                 const base::TimeDelta rotation_period) {
116     // It's a test logic error for the key data to exist when calling SetupKey,
117     // because it will desync the in-memory proto from the underlying storage.
118     if (key_data_) {
119       return false;
120     }
121 
122     KeyProto key_proto;
123     key_proto.set_key(key);
124     key_proto.set_last_rotation(last_rotation.InDays());
125     key_proto.set_rotation_period(rotation_period.InDays());
126 
127     ScopedDictPrefUpdate pref_updater(&prefs_, kTestPrefName);
128 
129     base::Value::Dict& dict = pref_updater.Get();
130     const validator::Validators* validators = validator::Validators::Get();
131     auto project_name = validators->GetProjectName(project_name_hash);
132 
133     auto value = util::CreateValueFromKeyProto(key_proto);
134 
135     dict.Set(*project_name, std::move(value));
136     return true;
137   }
138 
ExpectKeyValidation(const int valid,const int created,const int rotated)139   void ExpectKeyValidation(const int valid,
140                            const int created,
141                            const int rotated) {
142     static constexpr char kHistogram[] =
143         "UMA.StructuredMetrics.KeyValidationState";
144     histogram_tester_.ExpectBucketCount(kHistogram, KeyValidationState::kValid,
145                                         valid);
146     histogram_tester_.ExpectBucketCount(kHistogram,
147                                         KeyValidationState::kCreated, created);
148     histogram_tester_.ExpectBucketCount(kHistogram,
149                                         KeyValidationState::kRotated, rotated);
150   }
151 
152  protected:
153   base::test::TaskEnvironment task_environment_{
154       base::test::TaskEnvironment::MainThreadType::UI,
155       base::test::TaskEnvironment::ThreadPoolExecutionMode::QUEUED,
156       base::test::TaskEnvironment::TimeSource::MOCK_TIME};
157   TestingPrefServiceSimple prefs_;
158   base::HistogramTester histogram_tester_;
159 
160   std::unique_ptr<KeyData> key_data_;
161   raw_ptr<KeyDataPrefsDelegate> delegate_;
162 };
163 
164 // If there is no key store file present, check that new keys are generated for
165 // each project, and those keys are of the right length and different from each
166 // other.
TEST_F(KeyDataPrefsDelegateTest,GeneratesKeysForProjects)167 TEST_F(KeyDataPrefsDelegateTest, GeneratesKeysForProjects) {
168   // Make key data and use two keys, in order to generate them.
169   CreateKeyData();
170   key_data_->Id(kProjectOneHash, kKeyRotationPeriod);
171   key_data_->Id(kProjectTwoHash, kKeyRotationPeriod);
172 
173   const std::string key_one = GetKey(kProjectOneHash).key();
174   const std::string key_two = GetKey(kProjectTwoHash).key();
175 
176   EXPECT_EQ(key_one.size(), 32ul);
177   EXPECT_EQ(key_two.size(), 32ul);
178   EXPECT_NE(key_one, key_two);
179 
180   ExpectKeyValidation(/*valid=*/0, /*created=*/2, /*rotated=*/0);
181 }
182 
183 // If there is an existing key store file, check that its keys are not replaced.
TEST_F(KeyDataPrefsDelegateTest,ReuseExistingKeys)184 TEST_F(KeyDataPrefsDelegateTest, ReuseExistingKeys) {
185   // Create a file with one key.
186   CreateKeyData();
187   const uint64_t id_one = key_data_->Id(kProjectOneHash, kKeyRotationPeriod);
188   ExpectKeyValidation(/*valid=*/0, /*created=*/1, /*rotated=*/0);
189   const std::string key_one = GetKey(kProjectOneHash).key();
190 
191   // Reset the in-memory state, leave the on-disk state intact.
192   ResetKeyData();
193 
194   // Open the file again and check we use the same key.
195   CreateKeyData();
196   const uint64_t id_two = key_data_->Id(kProjectOneHash, kKeyRotationPeriod);
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),
210             key_data_->HmacMetric(kProjectTwoHash, kMetricOneHash, "value",
211                                   kKeyRotationPeriod));
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),
220             key_data_->HmacMetric(kProjectOneHash, kMetricTwoHash, "value",
221                                   kKeyRotationPeriod));
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),
230             key_data_->HmacMetric(kProjectOneHash, kMetricOneHash, "second",
231                                   kKeyRotationPeriod));
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(HashToHex(key_data_->Id(kProjectOneHash, kKeyRotationPeriod)),
240             kUserId);
241   EXPECT_NE(HashToHex(key_data_->Id(kProjectTwoHash, kKeyRotationPeriod)),
242             kUserId);
243 }
244 
245 // Ensure that KeyDataFileDelegate::Hash returns expected values for a known
246 // key / and value.
TEST_F(KeyDataPrefsDelegateTest,CheckHashes)247 TEST_F(KeyDataPrefsDelegateTest, CheckHashes) {
248   ASSERT_TRUE(SetupKey(kProjectOneHash, kKey, Today(), kKeyRotationPeriod));
249 
250   CreateKeyData();
251   EXPECT_EQ(HashToHex(key_data_->HmacMetric(kProjectOneHash, kMetricOneHash,
252                                             kValueOne, kKeyRotationPeriod)),
253             kValueOneHash);
254   EXPECT_EQ(HashToHex(key_data_->HmacMetric(kProjectOneHash, kMetricTwoHash,
255                                             kValueTwo, kKeyRotationPeriod)),
256             kValueTwoHash);
257 }
258 
259 //// Check that keys for a event are correctly rotated after a given rotation
260 //// period.
TEST_F(KeyDataPrefsDelegateTest,KeysRotated)261 TEST_F(KeyDataPrefsDelegateTest, KeysRotated) {
262   const base::TimeDelta start_day = Today();
263   ASSERT_TRUE(SetupKey(kProjectOneHash, kKey, start_day, kKeyRotationPeriod));
264 
265   CreateKeyData();
266   const uint64_t first_id = key_data_->Id(kProjectOneHash, kKeyRotationPeriod);
267   EXPECT_EQ(key_data_->LastKeyRotation(kProjectOneHash)->InDays(),
268             start_day.InDays());
269   ExpectKeyValidation(/*valid=*/1, /*created=*/0, /*rotated=*/0);
270 
271   {
272     // Advancing by |kKeyRotationPeriod|-1 days, the key should not be rotated.
273     task_environment_.AdvanceClock(kKeyRotationPeriod - base::Days(1));
274     EXPECT_EQ(key_data_->Id(kProjectOneHash, kKeyRotationPeriod), first_id);
275     EXPECT_EQ(key_data_->LastKeyRotation(kProjectOneHash)->InDays(),
276               start_day.InDays());
277 
278     ASSERT_EQ(GetKey(kProjectOneHash).last_rotation(), start_day.InDays());
279     ExpectKeyValidation(/*valid=*/2, /*created=*/0, /*rotated=*/0);
280   }
281 
282   {
283     // Advancing by another |key_rotation_period|+1 days, the key should be
284     // rotated and the last rotation day should be incremented by
285     // |key_rotation_period|.
286     task_environment_.AdvanceClock(kKeyRotationPeriod + base::Days(1));
287     EXPECT_NE(key_data_->Id(kProjectOneHash, kKeyRotationPeriod), first_id);
288 
289     base::TimeDelta expected_last_key_rotation =
290         start_day + 2 * kKeyRotationPeriod;
291     EXPECT_EQ(GetKey(kProjectOneHash).last_rotation(),
292               expected_last_key_rotation.InDays());
293     EXPECT_EQ(key_data_->LastKeyRotation(kProjectOneHash)->InDays(),
294               expected_last_key_rotation.InDays());
295     ExpectKeyValidation(/*valid=*/2, /*created=*/0, /*rotated=*/1);
296 
297     ASSERT_EQ(GetKey(kProjectOneHash).rotation_period(),
298               kKeyRotationPeriod.InDays());
299   }
300 
301   {
302     // Advancing by |2* kKeyRotationPeriod| days, the last rotation day should
303     // now 4 periods of |kKeyRotationPeriod| days ahead.
304     task_environment_.AdvanceClock(kKeyRotationPeriod * 2);
305     key_data_->Id(kProjectOneHash, kKeyRotationPeriod);
306 
307     base::TimeDelta expected_last_key_rotation =
308         start_day + 4 * kKeyRotationPeriod;
309     EXPECT_EQ(GetKey(kProjectOneHash).last_rotation(),
310               expected_last_key_rotation.InDays());
311     EXPECT_EQ(key_data_->LastKeyRotation(kProjectOneHash)->InDays(),
312               expected_last_key_rotation.InDays());
313     ExpectKeyValidation(/*valid=*/2, /*created=*/0, /*rotated=*/2);
314   }
315 }
316 
317 //// Check that keys with updated rotations are correctly rotated.
TEST_F(KeyDataPrefsDelegateTest,KeysWithUpdatedRotations)318 TEST_F(KeyDataPrefsDelegateTest, KeysWithUpdatedRotations) {
319   base::TimeDelta first_key_rotation_period = base::Days(60);
320 
321   const base::TimeDelta start_day = Today();
322   ASSERT_TRUE(
323       SetupKey(kProjectOneHash, kKey, start_day, first_key_rotation_period));
324 
325   CreateKeyData();
326   const uint64_t first_id =
327       key_data_->Id(kProjectOneHash, first_key_rotation_period);
328   EXPECT_EQ(key_data_->LastKeyRotation(kProjectOneHash)->InDays(),
329             start_day.InDays());
330   ExpectKeyValidation(/*valid=*/1, /*created=*/0, /*rotated=*/0);
331 
332   // Advance days by |new_key_rotation_period| + 1. This should fall within
333   // the rotation of the |new_key_rotation_period| but outside
334   // |first_key_rotation_period|.
335   const base::TimeDelta new_key_rotation_period = base::Days(50);
336   task_environment_.AdvanceClock(
337       base::Days(new_key_rotation_period.InDays() + 1));
338   const uint64_t second_id =
339       key_data_->Id(kProjectOneHash, new_key_rotation_period);
340   EXPECT_NE(first_id, second_id);
341 
342   // Key should have been rotated with new_key_rotation_period.
343   base::TimeDelta expected_last_key_rotation =
344       start_day + new_key_rotation_period;
345   EXPECT_EQ(GetKey(kProjectOneHash).last_rotation(),
346             expected_last_key_rotation.InDays());
347   EXPECT_EQ(key_data_->LastKeyRotation(kProjectOneHash)->InDays(),
348             expected_last_key_rotation.InDays());
349   ExpectKeyValidation(/*valid=*/1, /*created=*/0, /*rotated=*/1);
350 }
351 
TEST_F(KeyDataPrefsDelegateTest,Purge)352 TEST_F(KeyDataPrefsDelegateTest, Purge) {
353   const base::TimeDelta start_day = Today();
354   ASSERT_TRUE(SetupKey(kProjectOneHash, kKey, start_day, kKeyRotationPeriod));
355 
356   CreateKeyData();
357   EXPECT_EQ(delegate_->proto_.keys().size(), 1ul);
358 
359   key_data_->Purge();
360   EXPECT_EQ(delegate_->proto_.keys().size(), 0ul);
361 
362   const base::Value::Dict& keys_dict = prefs_.GetDict(kTestPrefName);
363   EXPECT_EQ(keys_dict.size(), 0ul);
364 }
365 
366 }  // namespace metrics::structured
367