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/lib/key_data.h"
6
7 #include <memory>
8 #include <utility>
9
10 #include "base/check.h"
11 #include "base/logging.h"
12 #include "base/notreached.h"
13 #include "base/rand_util.h"
14 #include "base/strings/strcat.h"
15 #include "base/strings/string_number_conversions.h"
16 #include "base/time/time.h"
17 #include "base/unguessable_token.h"
18 #include "components/metrics/structured/lib/histogram_util.h"
19 #include "components/metrics/structured/lib/key_util.h"
20 #include "crypto/hmac.h"
21 #include "crypto/sha2.h"
22
23 namespace metrics::structured {
24 namespace {
25
HashToHex(const uint64_t hash)26 std::string HashToHex(const uint64_t hash) {
27 return base::HexEncode(&hash, sizeof(uint64_t));
28 }
29
NowInDays()30 int NowInDays() {
31 return (base::Time::Now() - base::Time::UnixEpoch()).InDays();
32 }
33
34 } // namespace
35
KeyData(std::unique_ptr<StorageDelegate> storage_delegate)36 KeyData::KeyData(std::unique_ptr<StorageDelegate> storage_delegate)
37 : storage_delegate_(std::move(storage_delegate)) {
38 CHECK(storage_delegate_);
39 }
40 KeyData::~KeyData() = default;
41
Id(const uint64_t project_name_hash,base::TimeDelta key_rotation_period)42 uint64_t KeyData::Id(const uint64_t project_name_hash,
43 base::TimeDelta key_rotation_period) {
44 if (!storage_delegate_->IsReady()) {
45 return 0u;
46 }
47
48 // Retrieve the key for |project_name_hash|.
49 EnsureKeyUpdated(project_name_hash, key_rotation_period);
50 const std::optional<std::string_view> key = GetKeyBytes(project_name_hash);
51 CHECK(key);
52
53 // Compute and return the hash.
54 uint64_t hash;
55 crypto::SHA256HashString(key.value(), &hash, sizeof(uint64_t));
56 return hash;
57 }
58
HmacMetric(const uint64_t project_name_hash,const uint64_t metric_name_hash,const std::string & value,base::TimeDelta key_rotation_period)59 uint64_t KeyData::HmacMetric(const uint64_t project_name_hash,
60 const uint64_t metric_name_hash,
61 const std::string& value,
62 base::TimeDelta key_rotation_period) {
63 if (!storage_delegate_->IsReady()) {
64 return 0u;
65 }
66
67 // Retrieve the key for |project_name_hash|.
68 EnsureKeyUpdated(project_name_hash, key_rotation_period);
69 const std::optional<std::string_view> key = GetKeyBytes(project_name_hash);
70 CHECK(key);
71
72 // Initialize the HMAC.
73 crypto::HMAC hmac(crypto::HMAC::HashAlgorithm::SHA256);
74 CHECK(hmac.Init(key.value()));
75
76 // Compute and return the digest.
77 const std::string salted_value =
78 base::StrCat({HashToHex(metric_name_hash), value});
79 uint64_t digest;
80 CHECK(hmac.Sign(salted_value, reinterpret_cast<uint8_t*>(&digest),
81 sizeof(digest)));
82 return digest;
83 }
84
LastKeyRotation(const uint64_t project_name_hash) const85 std::optional<base::TimeDelta> KeyData::LastKeyRotation(
86 const uint64_t project_name_hash) const {
87 const KeyProto* key = storage_delegate_->GetKey(project_name_hash);
88 if (!key) {
89 return std::nullopt;
90 }
91 return base::Days(key->last_rotation());
92 }
93
GetKeyAgeInWeeks(uint64_t project_name_hash) const94 std::optional<int> KeyData::GetKeyAgeInWeeks(uint64_t project_name_hash) const {
95 std::optional<base::TimeDelta> last_rotation =
96 LastKeyRotation(project_name_hash);
97 if (!last_rotation.has_value()) {
98 return std::nullopt;
99 }
100 const int now = NowInDays();
101 const int days_since_rotation = now - last_rotation->InDays();
102 return days_since_rotation / 7;
103 }
104
Purge()105 void KeyData::Purge() {
106 storage_delegate_->Purge();
107 }
108
EnsureKeyUpdated(const uint64_t project_name_hash,base::TimeDelta key_rotation_period)109 void KeyData::EnsureKeyUpdated(const uint64_t project_name_hash,
110 base::TimeDelta key_rotation_period) {
111 CHECK(storage_delegate_->IsReady());
112
113 const int now = NowInDays();
114 const int key_rotation_period_days = key_rotation_period.InDays();
115 const KeyProto* key = storage_delegate_->GetKey(project_name_hash);
116
117 // Generate or rotate key.
118 if (!key || key->last_rotation() == 0) {
119 LogKeyValidation(KeyValidationState::kCreated);
120 // If the key does not exist, generate a new one. Set the last rotation to a
121 // uniformly selected day between today and |key_rotation_period| days
122 // ago, to uniformly distribute users amongst rotation cohorts.
123 const int rotation_seed = base::RandInt(0, key_rotation_period_days - 1);
124 storage_delegate_->UpsertKey(project_name_hash,
125 base::Days(now - rotation_seed),
126 key_rotation_period);
127 } else if (now - key->last_rotation() > key_rotation_period_days) {
128 LogKeyValidation(KeyValidationState::kRotated);
129 // If the key is outdated, generate a new one. Update the last rotation
130 // such that the user stays in the same cohort.
131 //
132 // Note that if the max key rotation period has changed, the new rotation
133 // period will be used to calculate whether the key should be rotated or
134 // not.
135 const int new_last_rotation =
136 now - (now - key->last_rotation()) % key_rotation_period_days;
137 storage_delegate_->UpsertKey(
138 project_name_hash, base::Days(new_last_rotation), key_rotation_period);
139 } else {
140 LogKeyValidation(KeyValidationState::kValid);
141 }
142 }
143
GetKeyBytes(const uint64_t project_name_hash) const144 const std::optional<std::string_view> KeyData::GetKeyBytes(
145 const uint64_t project_name_hash) const {
146 // Re-fetch the key after keys are rotated.
147 const KeyProto* key = storage_delegate_->GetKey(project_name_hash);
148 if (!key) {
149 return std::nullopt;
150 }
151
152 // Return the key unless it's the wrong size, in which case return nullopt.
153 const std::string_view key_string = key->key();
154 if (key_string.size() != kKeySize) {
155 return std::nullopt;
156 }
157 return key_string;
158 }
159
160 } // namespace metrics::structured
161