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