xref: /aosp_15_r20/external/cronet/components/metrics/structured/key_data_prefs_delegate_unittest.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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