xref: /aosp_15_r20/external/cronet/components/metrics/structured/lib/key_data.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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,int key_rotation_period)42 uint64_t KeyData::Id(const uint64_t project_name_hash,
43                      int 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, base::Days(key_rotation_period));
50   const std::optional<std::string_view> key = GetKeyBytes(project_name_hash);
51   if (!key) {
52     NOTREACHED();
53     return 0u;
54   }
55 
56   // Compute and return the hash.
57   uint64_t hash;
58   crypto::SHA256HashString(key.value(), &hash, sizeof(uint64_t));
59   return hash;
60 }
61 
HmacMetric(const uint64_t project_name_hash,const uint64_t metric_name_hash,const std::string & value,int key_rotation_period)62 uint64_t KeyData::HmacMetric(const uint64_t project_name_hash,
63                              const uint64_t metric_name_hash,
64                              const std::string& value,
65                              int key_rotation_period) {
66   if (!storage_delegate_->IsReady()) {
67     return 0u;
68   }
69 
70   // Retrieve the key for |project_name_hash|.
71   EnsureKeyUpdated(project_name_hash, base::Days(key_rotation_period));
72   const std::optional<std::string_view> key = GetKeyBytes(project_name_hash);
73   if (!key) {
74     NOTREACHED();
75     return 0u;
76   }
77 
78   // Initialize the HMAC.
79   crypto::HMAC hmac(crypto::HMAC::HashAlgorithm::SHA256);
80   CHECK(hmac.Init(key.value()));
81 
82   // Compute and return the digest.
83   const std::string salted_value =
84       base::StrCat({HashToHex(metric_name_hash), value});
85   uint64_t digest;
86   CHECK(hmac.Sign(salted_value, reinterpret_cast<uint8_t*>(&digest),
87                   sizeof(digest)));
88   return digest;
89 }
90 
LastKeyRotation(const uint64_t project_name_hash) const91 std::optional<int> KeyData::LastKeyRotation(
92     const uint64_t project_name_hash) const {
93   const KeyProto* key = storage_delegate_->GetKey(project_name_hash);
94   if (!key) {
95     return std::nullopt;
96   }
97   return key->last_rotation();
98 }
99 
GetKeyAgeInWeeks(uint64_t project_name_hash) const100 std::optional<int> KeyData::GetKeyAgeInWeeks(uint64_t project_name_hash) const {
101   std::optional<int> last_rotation = LastKeyRotation(project_name_hash);
102   if (!last_rotation.has_value()) {
103     return std::nullopt;
104   }
105   const int now = NowInDays();
106   const int days_since_rotation = now - *last_rotation;
107   return days_since_rotation / 7;
108 }
109 
Purge()110 void KeyData::Purge() {
111   storage_delegate_->Purge();
112 }
113 
EnsureKeyUpdated(const uint64_t project_name_hash,base::TimeDelta key_rotation_period)114 void KeyData::EnsureKeyUpdated(const uint64_t project_name_hash,
115                                base::TimeDelta key_rotation_period) {
116   CHECK(storage_delegate_->IsReady());
117 
118   const int now = NowInDays();
119   const int key_rotation_period_days = key_rotation_period.InDays();
120   const KeyProto* key = storage_delegate_->GetKey(project_name_hash);
121 
122   // Generate or rotate key.
123   if (!key || key->last_rotation() == 0) {
124     LogKeyValidation(KeyValidationState::kCreated);
125     // If the key does not exist, generate a new one. Set the last rotation to a
126     // uniformly selected day between today and |key_rotation_period| days
127     // ago, to uniformly distribute users amongst rotation cohorts.
128     const int rotation_seed = base::RandInt(0, key_rotation_period_days - 1);
129     storage_delegate_->UpsertKey(project_name_hash,
130                                  base::Days(now - rotation_seed),
131                                  key_rotation_period);
132   } else if (now - key->last_rotation() > key_rotation_period_days) {
133     LogKeyValidation(KeyValidationState::kRotated);
134     // If the key is outdated, generate a new one. Update the last rotation
135     // such that the user stays in the same cohort.
136     //
137     // Note that if the max key rotation period has changed, the new rotation
138     // period will be used to calculate whether the key should be rotated or
139     // not.
140     const int new_last_rotation =
141         now - (now - key->last_rotation()) % key_rotation_period_days;
142     storage_delegate_->UpsertKey(
143         project_name_hash, base::Days(new_last_rotation), key_rotation_period);
144   } else {
145     LogKeyValidation(KeyValidationState::kValid);
146   }
147 }
148 
GetKeyBytes(const uint64_t project_name_hash) const149 const std::optional<std::string_view> KeyData::GetKeyBytes(
150     const uint64_t project_name_hash) const {
151   // Re-fetch the key after keys are rotated.
152   const KeyProto* key = storage_delegate_->GetKey(project_name_hash);
153   if (!key) {
154     return std::nullopt;
155   }
156 
157   // Return the key unless it's the wrong size, in which case return nullopt.
158   const std::string_view key_string = key->key();
159   if (key_string.size() != kKeySize) {
160     return std::nullopt;
161   }
162   return key_string;
163 }
164 
165 }  // namespace metrics::structured
166