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