• 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 
9 #include "base/logging.h"
10 #include "base/rand_util.h"
11 #include "base/strings/strcat.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "base/time/time.h"
14 #include "base/unguessable_token.h"
15 #include "components/metrics/structured/histogram_util.h"
16 #include "crypto/hmac.h"
17 #include "crypto/sha2.h"
18 
19 namespace metrics::structured {
20 namespace {
21 
22 // The expected size of a key, in bytes.
23 constexpr size_t kKeySize = 32;
24 
25 // Generates a key, which is the string representation of
26 // base::UnguessableToken, and is of size |kKeySize| bytes.
GenerateKey()27 std::string GenerateKey() {
28   const std::string key = base::UnguessableToken::Create().ToString();
29   DCHECK_EQ(key.size(), kKeySize);
30   return key;
31 }
32 
HashToHex(const uint64_t hash)33 std::string HashToHex(const uint64_t hash) {
34   return base::HexEncode(&hash, sizeof(uint64_t));
35 }
36 
NowInDays()37 int NowInDays() {
38   return (base::Time::Now() - base::Time::UnixEpoch()).InDays();
39 }
40 
41 }  // namespace
42 
KeyData(const base::FilePath & path,const base::TimeDelta & save_delay,base::OnceCallback<void ()> on_initialized)43 KeyData::KeyData(const base::FilePath& path,
44                  const base::TimeDelta& save_delay,
45                  base::OnceCallback<void()> on_initialized)
46     : on_initialized_(std::move(on_initialized)) {
47   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
48   proto_ = std::make_unique<PersistentProto<KeyDataProto>>(
49       path, save_delay,
50       base::BindOnce(&KeyData::OnRead, weak_factory_.GetWeakPtr()),
51       base::BindRepeating(&KeyData::OnWrite, weak_factory_.GetWeakPtr()));
52 }
53 
54 KeyData::~KeyData() = default;
55 
OnRead(const ReadStatus status)56 void KeyData::OnRead(const ReadStatus status) {
57   is_initialized_ = true;
58   switch (status) {
59     case ReadStatus::kOk:
60     case ReadStatus::kMissing:
61       break;
62     case ReadStatus::kReadError:
63       LogInternalError(StructuredMetricsError::kKeyReadError);
64       break;
65     case ReadStatus::kParseError:
66       LogInternalError(StructuredMetricsError::kKeyParseError);
67       break;
68   }
69 
70   std::move(on_initialized_).Run();
71 }
72 
OnWrite(const WriteStatus status)73 void KeyData::OnWrite(const WriteStatus status) {
74   switch (status) {
75     case WriteStatus::kOk:
76       break;
77     case WriteStatus::kWriteError:
78       LogInternalError(StructuredMetricsError::kKeyWriteError);
79       break;
80     case WriteStatus::kSerializationError:
81       LogInternalError(StructuredMetricsError::kKeySerializationError);
82       break;
83   }
84 }
85 
WriteNowForTest()86 void KeyData::WriteNowForTest() {
87   proto_.get()->StartWrite();
88 }
89 
90 //---------------
91 // Key management
92 //---------------
93 
ValidateAndGetKey(const uint64_t project_name_hash,int key_rotation_period)94 absl::optional<std::string> KeyData::ValidateAndGetKey(
95     const uint64_t project_name_hash,
96     int key_rotation_period) {
97   if (!is_initialized_) {
98     NOTREACHED();
99     return absl::nullopt;
100   }
101 
102   const int now = NowInDays();
103   KeyProto& key = (*(proto_.get()->get()->mutable_keys()))[project_name_hash];
104 
105   // Generate or rotate key.
106   const int last_rotation = key.last_rotation();
107 
108   if (key.key().empty() || last_rotation == 0) {
109     LogKeyValidation(KeyValidationState::kCreated);
110     // If the key is empty, generate a new one. Set the last rotation to a
111     // uniformly selected day between today and |key_rotation_period| days
112     // ago, to uniformly distribute users amongst rotation cohorts.
113     const int rotation_seed = base::RandInt(0, key_rotation_period - 1);
114     UpdateKey(&key, now - rotation_seed, key_rotation_period);
115   } else if (now - last_rotation > key_rotation_period) {
116     LogKeyValidation(KeyValidationState::kRotated);
117 
118     // If the key is outdated, generate a new one. Update the last rotation
119     // such that the user stays in the same cohort.
120     //
121     // Note that if the max key rotation period has changed, the new rotation
122     // period will be used to calculate whether the key should be rotated or
123     // not.
124     const int new_last_rotation =
125         now - (now - last_rotation) % key_rotation_period;
126     UpdateKey(&key, new_last_rotation, key_rotation_period);
127   } else {
128     LogKeyValidation(KeyValidationState::kValid);
129   }
130 
131   // Return the key unless it's the wrong size, in which case return nullopt.
132   const std::string key_string = key.key();
133   if (key_string.size() != kKeySize) {
134     LogInternalError(StructuredMetricsError::kWrongKeyLength);
135     return absl::nullopt;
136   }
137   return key_string;
138 }
139 
UpdateKey(KeyProto * key,int last_key_rotation,int key_rotation_period)140 void KeyData::UpdateKey(KeyProto* key,
141                         int last_key_rotation,
142                         int key_rotation_period) {
143   key->set_key(GenerateKey());
144   key->set_last_rotation(last_key_rotation);
145   key->set_rotation_period(key_rotation_period);
146   proto_->QueueWrite();
147 }
148 
149 //----------------
150 // IDs and hashing
151 //----------------
152 
Id(const uint64_t project_name_hash,int key_rotation_period)153 uint64_t KeyData::Id(const uint64_t project_name_hash,
154                      int key_rotation_period) {
155   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
156 
157   // Retrieve the key for |project_name_hash|.
158   const absl::optional<std::string> key =
159       ValidateAndGetKey(project_name_hash, key_rotation_period);
160   if (!key) {
161     NOTREACHED();
162     return 0u;
163   }
164 
165   // Compute and return the hash.
166   uint64_t hash;
167   crypto::SHA256HashString(key.value(), &hash, sizeof(uint64_t));
168   return hash;
169 }
170 
HmacMetric(const uint64_t project_name_hash,const uint64_t metric_name_hash,const std::string & value,int key_rotation_period)171 uint64_t KeyData::HmacMetric(const uint64_t project_name_hash,
172                              const uint64_t metric_name_hash,
173                              const std::string& value,
174                              int key_rotation_period) {
175   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
176 
177   // Retrieve the key for |project_name_hash|.
178   const absl::optional<std::string> key =
179       ValidateAndGetKey(project_name_hash, key_rotation_period);
180   if (!key) {
181     NOTREACHED();
182     return 0u;
183   }
184 
185   // Initialize the HMAC.
186   crypto::HMAC hmac(crypto::HMAC::HashAlgorithm::SHA256);
187   CHECK(hmac.Init(key.value()));
188 
189   // Compute and return the digest.
190   const std::string salted_value =
191       base::StrCat({HashToHex(metric_name_hash), value});
192   uint64_t digest;
193   CHECK(hmac.Sign(salted_value, reinterpret_cast<uint8_t*>(&digest),
194                   sizeof(digest)));
195   return digest;
196 }
197 
198 //-----
199 // Misc
200 //-----
201 
LastKeyRotation(const uint64_t project_name_hash) const202 absl::optional<int> KeyData::LastKeyRotation(
203     const uint64_t project_name_hash) const {
204   const auto& keys = proto_.get()->get()->keys();
205   const auto& it = keys.find(project_name_hash);
206   if (it != keys.end()) {
207     return it->second.last_rotation();
208   }
209   return absl::nullopt;
210 }
211 
GetKeyAgeInWeeks(uint64_t project_name_hash) const212 absl::optional<int> KeyData::GetKeyAgeInWeeks(
213     uint64_t project_name_hash) const {
214   absl::optional<int> last_rotation = LastKeyRotation(project_name_hash);
215   if (!last_rotation.has_value()) {
216     return absl::nullopt;
217   }
218   const int now = NowInDays();
219   const int days_since_rotation = now - *last_rotation;
220   return days_since_rotation / 7;
221 }
222 
Purge()223 void KeyData::Purge() {
224   proto_->Purge();
225 }
226 
227 }  // namespace metrics::structured
228