• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2019 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.h"
6 
7 #include <memory>
8 #include <string>
9 
10 #include "base/containers/flat_set.h"
11 #include "base/files/file_path.h"
12 #include "base/files/file_util.h"
13 #include "base/files/scoped_temp_dir.h"
14 #include "base/functional/callback_helpers.h"
15 #include "base/logging.h"
16 #include "base/run_loop.h"
17 #include "base/strings/strcat.h"
18 #include "base/strings/string_number_conversions.h"
19 #include "base/test/metrics/histogram_tester.h"
20 #include "base/test/task_environment.h"
21 #include "base/values.h"
22 #include "components/metrics/structured/histogram_util.h"
23 #include "components/metrics/structured/recorder.h"
24 #include "components/metrics/structured/storage.pb.h"
25 #include "components/prefs/persistent_pref_store.h"
26 #include "testing/gtest/include/gtest/gtest.h"
27 
28 namespace metrics::structured {
29 
30 namespace {
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 = UINT64_C(16881314472396226433);
44 // The name hash of "TestProjectTwo".
45 constexpr uint64_t kProjectTwoHash = UINT64_C(5876808001962504629);
46 
47 // The name hash of "TestMetricOne".
48 constexpr uint64_t kMetricOneHash = UINT64_C(637929385654885975);
49 // The name hash of "TestMetricTwo".
50 constexpr uint64_t kMetricTwoHash = UINT64_C(14083999144141567134);
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 int kKeyRotationPeriod = 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 
68 }  // namespace
69 
70 class KeyDataTest : public testing::Test {
71  protected:
SetUp()72   void SetUp() override {
73     ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
74     // Move the mock date forward from day 0, because KeyData assumes that day 0
75     // is a bug.
76     task_environment_.AdvanceClock(base::Days(1000));
77   }
78 
ResetState()79   void ResetState() {
80     key_data_.reset();
81     base::DeleteFile(GetPath());
82     ASSERT_FALSE(base::PathExists(GetPath()));
83   }
84 
GetPath()85   base::FilePath GetPath() {
86     return temp_dir_.GetPath().Append(FILE_PATH_LITERAL("keys"));
87   }
88 
MakeKeyData()89   void MakeKeyData() {
90     key_data_ = std::make_unique<KeyData>(GetPath(), base::Seconds(0),
91                                           base::DoNothing());
92     Wait();
93   }
94 
SaveKeyData()95   void SaveKeyData() {
96     key_data_->WriteNowForTest();
97     Wait();
98     ASSERT_TRUE(base::PathExists(GetPath()));
99   }
100 
Today()101   int Today() { return (base::Time::Now() - base::Time::UnixEpoch()).InDays(); }
102 
103   // Read the on-disk file and return the information about the key for
104   // |project_name_hash|. Fails if a key does not exist.
GetKey(const uint64_t project_name_hash)105   KeyProto GetKey(const uint64_t project_name_hash) {
106     std::string proto_str;
107     CHECK(base::ReadFileToString(GetPath(), &proto_str));
108     KeyDataProto proto;
109     CHECK(proto.ParseFromString(proto_str));
110 
111     const auto it = proto.keys().find(project_name_hash);
112     CHECK(it != proto.keys().end());
113     return it->second;
114   }
115 
116   // Write a KeyDataProto to disk with a single key described by the arguments.
SetupKey(const uint64_t project_name_hash,const std::string & key,const int last_rotation,const int rotation_period)117   void SetupKey(const uint64_t project_name_hash,
118                 const std::string& key,
119                 const int last_rotation,
120                 const int rotation_period) {
121     // It's a test logic error for the key data to exist when calling SetupKey,
122     // because it will desync the in-memory proto from the underlying storage.
123     ASSERT_FALSE(key_data_);
124 
125     KeyDataProto proto;
126     KeyProto& key_proto = (*proto.mutable_keys())[project_name_hash];
127     key_proto.set_key(key);
128     key_proto.set_last_rotation(last_rotation);
129     key_proto.set_rotation_period(rotation_period);
130 
131     ASSERT_TRUE(base::WriteFile(GetPath(), proto.SerializeAsString()));
132   }
133 
Wait()134   void Wait() { task_environment_.RunUntilIdle(); }
135 
ExpectNoErrors()136   void ExpectNoErrors() {
137     histogram_tester_.ExpectTotalCount("UMA.StructuredMetrics.InternalError",
138                                        0);
139   }
140 
ExpectKeyValidation(const int valid,const int created,const int rotated)141   void ExpectKeyValidation(const int valid,
142                            const int created,
143                            const int rotated) {
144     static const std::string histogram =
145         "UMA.StructuredMetrics.KeyValidationState";
146     histogram_tester_.ExpectBucketCount(histogram, KeyValidationState::kValid,
147                                         valid);
148     histogram_tester_.ExpectBucketCount(histogram, KeyValidationState::kCreated,
149                                         created);
150     histogram_tester_.ExpectBucketCount(histogram, KeyValidationState::kRotated,
151                                         rotated);
152   }
153 
154   base::test::TaskEnvironment task_environment_{
155       base::test::TaskEnvironment::MainThreadType::UI,
156       base::test::TaskEnvironment::ThreadPoolExecutionMode::QUEUED,
157       base::test::TaskEnvironment::TimeSource::MOCK_TIME};
158   base::ScopedTempDir temp_dir_;
159   base::HistogramTester histogram_tester_;
160 
161   std::unique_ptr<KeyData> key_data_;
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(KeyDataTest,GeneratesKeysForProjects)167 TEST_F(KeyDataTest, GeneratesKeysForProjects) {
168   // Make key data and use two keys, in order to generate them.
169   MakeKeyData();
170   key_data_->Id(kProjectOneHash, kKeyRotationPeriod);
171   key_data_->Id(kProjectTwoHash, kKeyRotationPeriod);
172   SaveKeyData();
173 
174   const std::string key_one = GetKey(kProjectOneHash).key();
175   const std::string key_two = GetKey(kProjectTwoHash).key();
176 
177   EXPECT_EQ(key_one.size(), 32ul);
178   EXPECT_EQ(key_two.size(), 32ul);
179   EXPECT_NE(key_one, key_two);
180 
181   ExpectNoErrors();
182   ExpectKeyValidation(/*valid=*/0, /*created=*/2, /*rotated=*/0);
183 }
184 
185 // When repeatedly initialized with no key store file present, ensure the keys
186 // generated each time are distinct.
TEST_F(KeyDataTest,GeneratesDistinctKeys)187 TEST_F(KeyDataTest, GeneratesDistinctKeys) {
188   base::flat_set<std::string> keys;
189 
190   for (int i = 1; i <= 10; ++i) {
191     // Reset on-disk and in-memory state, regenerate the key, and save it to
192     // disk.
193     ResetState();
194     MakeKeyData();
195     key_data_->Id(kProjectOneHash, kKeyRotationPeriod);
196     SaveKeyData();
197 
198     keys.insert(GetKey(kProjectOneHash).key());
199     ExpectKeyValidation(/*valid=*/0, /*created=*/i, /*rotated=*/0);
200   }
201 
202   ExpectNoErrors();
203   EXPECT_EQ(keys.size(), 10ul);
204 }
205 
206 // If there is an existing key store file, check that its keys are not replaced.
TEST_F(KeyDataTest,ReuseExistingKeys)207 TEST_F(KeyDataTest, ReuseExistingKeys) {
208   // Create a file with one key.
209   MakeKeyData();
210   const uint64_t id_one = key_data_->Id(kProjectOneHash, kKeyRotationPeriod);
211   SaveKeyData();
212   ExpectKeyValidation(/*valid=*/0, /*created=*/1, /*rotated=*/0);
213   const std::string key_one = GetKey(kProjectOneHash).key();
214 
215   // Reset the in-memory state, leave the on-disk state intact.
216   key_data_.reset();
217 
218   // Open the file again and check we use the same key.
219   MakeKeyData();
220   const uint64_t id_two = key_data_->Id(kProjectOneHash, kKeyRotationPeriod);
221   ExpectKeyValidation(/*valid=*/1, /*created=*/1, /*rotated=*/0);
222   SaveKeyData();
223   const std::string key_two = GetKey(kProjectOneHash).key();
224 
225   EXPECT_EQ(id_one, id_two);
226   EXPECT_EQ(key_one, key_two);
227 }
228 
229 // Check that different events have different hashes for the same metric and
230 // value.
TEST_F(KeyDataTest,DifferentEventsDifferentHashes)231 TEST_F(KeyDataTest, DifferentEventsDifferentHashes) {
232   MakeKeyData();
233   EXPECT_NE(key_data_->HmacMetric(kProjectOneHash, kMetricOneHash, "value",
234                                   kKeyRotationPeriod),
235             key_data_->HmacMetric(kProjectTwoHash, kMetricOneHash, "value",
236                                   kKeyRotationPeriod));
237   ExpectNoErrors();
238 }
239 
240 // Check that an event has different hashes for different metrics with the same
241 // value.
TEST_F(KeyDataTest,DifferentMetricsDifferentHashes)242 TEST_F(KeyDataTest, DifferentMetricsDifferentHashes) {
243   MakeKeyData();
244   EXPECT_NE(key_data_->HmacMetric(kProjectOneHash, kMetricOneHash, "value",
245                                   kKeyRotationPeriod),
246             key_data_->HmacMetric(kProjectOneHash, kMetricTwoHash, "value",
247                                   kKeyRotationPeriod));
248   ExpectNoErrors();
249 }
250 
251 // Check that an event has different hashes for different values of the same
252 // metric.
TEST_F(KeyDataTest,DifferentValuesDifferentHashes)253 TEST_F(KeyDataTest, DifferentValuesDifferentHashes) {
254   MakeKeyData();
255   EXPECT_NE(key_data_->HmacMetric(kProjectOneHash, kMetricOneHash, "first",
256                                   kKeyRotationPeriod),
257             key_data_->HmacMetric(kProjectOneHash, kMetricOneHash, "second",
258                                   kKeyRotationPeriod));
259   ExpectNoErrors();
260 }
261 
262 // Ensure that KeyData::UserId is the expected value of SHA256(key).
TEST_F(KeyDataTest,CheckUserIDs)263 TEST_F(KeyDataTest, CheckUserIDs) {
264   SetupKey(kProjectOneHash, kKey, Today(), kKeyRotationPeriod);
265 
266   MakeKeyData();
267   EXPECT_EQ(HashToHex(key_data_->Id(kProjectOneHash, kKeyRotationPeriod)),
268             kUserId);
269   EXPECT_NE(HashToHex(key_data_->Id(kProjectTwoHash, kKeyRotationPeriod)),
270             kUserId);
271   ExpectKeyValidation(/*valid=*/1, /*created=*/1, /*rotated=*/0);
272   ExpectNoErrors();
273 }
274 
275 // Ensure that KeyData::Hash returns expected values for a known key and value.
TEST_F(KeyDataTest,CheckHashes)276 TEST_F(KeyDataTest, CheckHashes) {
277   SetupKey(kProjectOneHash, kKey, Today(), kKeyRotationPeriod);
278 
279   MakeKeyData();
280   EXPECT_EQ(HashToHex(key_data_->HmacMetric(kProjectOneHash, kMetricOneHash,
281                                             kValueOne, kKeyRotationPeriod)),
282             kValueOneHash);
283   EXPECT_EQ(HashToHex(key_data_->HmacMetric(kProjectOneHash, kMetricTwoHash,
284                                             kValueTwo, kKeyRotationPeriod)),
285             kValueTwoHash);
286   ExpectKeyValidation(/*valid=*/2, /*created=*/0, /*rotated=*/0);
287   ExpectNoErrors();
288 }
289 
290 // Check that keys for a event are correctly rotated after a given rotation
291 // period.
TEST_F(KeyDataTest,KeysRotated)292 TEST_F(KeyDataTest, KeysRotated) {
293   const int start_day = Today();
294   SetupKey(kProjectOneHash, kKey, start_day, kKeyRotationPeriod);
295 
296   MakeKeyData();
297   const uint64_t first_id = key_data_->Id(kProjectOneHash, kKeyRotationPeriod);
298   EXPECT_EQ(key_data_->LastKeyRotation(kProjectOneHash), start_day);
299   ExpectKeyValidation(/*valid=*/1, /*created=*/0, /*rotated=*/0);
300 
301   {
302     // Advancing by |kKeyRotationPeriod|-1 days, the key should not be rotated.
303     task_environment_.AdvanceClock(base::Days(kKeyRotationPeriod - 1));
304     EXPECT_EQ(key_data_->Id(kProjectOneHash, kKeyRotationPeriod), first_id);
305     EXPECT_EQ(key_data_->LastKeyRotation(kProjectOneHash), start_day);
306     SaveKeyData();
307 
308     ASSERT_EQ(GetKey(kProjectOneHash).last_rotation(), start_day);
309     ExpectKeyValidation(/*valid=*/2, /*created=*/0, /*rotated=*/0);
310   }
311 
312   {
313     // Advancing by another |key_rotation_period|+1 days, the key should be
314     // rotated and the last rotation day should be incremented by
315     // |key_rotation_period|.
316     task_environment_.AdvanceClock(base::Days(kKeyRotationPeriod + 1));
317     EXPECT_NE(key_data_->Id(kProjectOneHash, kKeyRotationPeriod), first_id);
318     SaveKeyData();
319 
320     int expected_last_key_rotation = start_day + 2 * kKeyRotationPeriod;
321     EXPECT_EQ(GetKey(kProjectOneHash).last_rotation(),
322               expected_last_key_rotation);
323     EXPECT_EQ(key_data_->LastKeyRotation(kProjectOneHash),
324               expected_last_key_rotation);
325     ExpectKeyValidation(/*valid=*/2, /*created=*/0, /*rotated=*/1);
326 
327     ASSERT_EQ(GetKey(kProjectOneHash).rotation_period(), kKeyRotationPeriod);
328   }
329 
330   {
331     // Advancing by |2* kKeyRotationPeriod| days, the last rotation day should
332     // now 4 periods of |kKeyRotationPeriod| days ahead.
333     task_environment_.AdvanceClock(base::Days(kKeyRotationPeriod * 2));
334     key_data_->Id(kProjectOneHash, kKeyRotationPeriod);
335     SaveKeyData();
336 
337     int expected_last_key_rotation = start_day + 4 * kKeyRotationPeriod;
338     EXPECT_EQ(GetKey(kProjectOneHash).last_rotation(),
339               expected_last_key_rotation);
340     EXPECT_EQ(key_data_->LastKeyRotation(kProjectOneHash),
341               expected_last_key_rotation);
342     ExpectKeyValidation(/*valid=*/2, /*created=*/0, /*rotated=*/2);
343   }
344 }
345 
346 // Check that keys with updated rotations are correctly rotated.
TEST_F(KeyDataTest,KeysWithUpdatedRotations)347 TEST_F(KeyDataTest, KeysWithUpdatedRotations) {
348   int first_key_rotation_period = 60;
349 
350   const int start_day = Today();
351   SetupKey(kProjectOneHash, kKey, start_day, first_key_rotation_period);
352 
353   MakeKeyData();
354   const uint64_t first_id =
355       key_data_->Id(kProjectOneHash, first_key_rotation_period);
356   EXPECT_EQ(key_data_->LastKeyRotation(kProjectOneHash), start_day);
357   ExpectKeyValidation(/*valid=*/1, /*created=*/0, /*rotated=*/0);
358 
359   // Advance days by |new_key_rotation_period| + 1. This should fall within the
360   // rotation of the |new_key_rotation_period| but outside
361   // |first_key_rotation_period|.
362   int new_key_rotation_period = 50;
363   task_environment_.AdvanceClock(base::Days(new_key_rotation_period + 1));
364   const uint64_t second_id =
365       key_data_->Id(kProjectOneHash, new_key_rotation_period);
366   EXPECT_NE(first_id, second_id);
367   SaveKeyData();
368 
369   // Key should have been rotated with new_key_rotation_period.
370   int expected_last_key_rotation = start_day + new_key_rotation_period;
371   EXPECT_EQ(GetKey(kProjectOneHash).last_rotation(),
372             expected_last_key_rotation);
373   EXPECT_EQ(key_data_->LastKeyRotation(kProjectOneHash),
374             expected_last_key_rotation);
375   ExpectKeyValidation(/*valid=*/1, /*created=*/0, /*rotated=*/1);
376 }
377 
378 }  // namespace metrics::structured
379