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 #ifndef COMPONENTS_METRICS_STRUCTURED_LIB_KEY_DATA_H_ 6 #define COMPONENTS_METRICS_STRUCTURED_LIB_KEY_DATA_H_ 7 8 #include <memory> 9 #include <optional> 10 #include <string> 11 #include <string_view> 12 13 #include "base/memory/scoped_refptr.h" 14 #include "base/memory/weak_ptr.h" 15 #include "base/sequence_checker.h" 16 #include "base/task/sequenced_task_runner.h" 17 #include "base/time/time.h" 18 #include "components/metrics/structured/lib/persistent_proto.h" 19 #include "components/metrics/structured/lib/proto/key.pb.h" 20 21 namespace metrics::structured { 22 23 // KeyData is the central class for managing keys and generating hashes for 24 // structured metrics. 25 // 26 // The class maintains one key and its rotation data for every project defined 27 // in /tools/metrics/structured/sync/structured.xml. This can be used to 28 // generate: 29 // - an ID for the project with KeyData::Id. 30 // - a hash of a given value for an event with KeyData::HmacMetric. 31 // 32 // Every project has a uint64_t project_name_hash that is generated by taking 33 // the first 8 bytes of MD5 hash of the project name. Keys for the project are 34 // retrieved using this project_name_hash. For more details, refer to 35 // //tools/metrics/structured/ccodegen.py. 36 // 37 // KeyData performs key rotation. Every project is associated with a rotation 38 // period, which is 90 days unless specified in structured.xml. Keys are rotated 39 // with a resolution of one day. They are guaranteed not to be used for 40 // HmacMetric or UserProjectId for longer than their rotation period, except in 41 // cases of local clock changes. 42 // 43 // When first created, every project's key rotation date is selected uniformly 44 // so that there is an even distribution of rotations across users. This means 45 // that, for most users, the first rotation period will be shorter than the 46 // standard full rotation period for that project. 47 class KeyData { 48 public: 49 // Delegate to read and upsert keys. 50 class StorageDelegate { 51 public: 52 virtual ~StorageDelegate() = default; 53 54 // Returns if the delegate is ready to read or upsert keys. 55 virtual bool IsReady() const = 0; 56 57 // Returns the key associated with |project_name_hash|. 58 // 59 // If the key does not exist yet, then returns nullptr. Note that this will 60 // return the expired key if it needs to be rotated. 61 virtual const KeyProto* GetKey(uint64_t project_name_hash) const = 0; 62 63 // Upserts the key for |project_name_hash| with duration 64 // |key_rotation_period| and last updated time |last_key_rotation|. 65 // 66 // |last_key_rotation| is the TimeDelta from base::Time::UnixEpoch in 67 // which the key was last rotated. 68 virtual void UpsertKey(uint64_t project_name_hash, 69 base::TimeDelta last_key_rotation, 70 base::TimeDelta key_rotation_period) = 0; 71 72 // Clears all key data. 73 virtual void Purge() = 0; 74 }; 75 76 // Key data will use |storage_delegate| to read and upsert keys. 77 explicit KeyData(std::unique_ptr<StorageDelegate> storage_delegate); 78 79 KeyData(const KeyData&) = delete; 80 KeyData& operator=(const KeyData&) = delete; 81 82 ~KeyData(); 83 84 // Returns a digest of |value| for |metric| in the context of 85 // |project_name_hash|. Terminology: a metric is a (name, value) pair, and an 86 // event is a bundle of metrics. Each event is associated with a project. 87 // 88 // - |project_name_hash| is the uint64 name hash of a project. 89 // - |metric_name_hash| is the uint64 name hash of a metric. 90 // - |value| is the string value to hash. 91 // 92 // The result is the HMAC digest of the |value| salted with |metric|, using 93 // the key for |project_name_hash|. That is: 94 // 95 // HMAC_SHA256(key(project_name_hash), concat(value, hex(event), 96 // hex(metric))) 97 // 98 // Returns 0u in case of an error. 99 // 100 // TODO(b/316419439): Change |key_rotation_period| to base::TimeDelta. 101 uint64_t HmacMetric(uint64_t project_name_hash, 102 uint64_t metric_name_hash, 103 const std::string& value, 104 int key_rotation_period); 105 106 // Returns an ID for this (user, |project_name_hash|) pair. 107 // |project_name_hash| is the name of a project, represented by the first 8 108 // bytes of the MD5 hash of its name defined in structured.xml. 109 // 110 // The derived ID is the first 8 bytes of SHA256(key(project_name_hash)). 111 // Returns 0u in case of an error. 112 // 113 // This ID is intended as the only ID for the events of a particular 114 // structured metrics project. However, events are uploaded from the device 115 // alongside the UMA client ID, which is only removed after the event reaches 116 // the server. This means events are associated with the client ID when 117 // uploaded from the device. See the class comment of 118 // StructuredMetricsProvider for more details. 119 // 120 // Default |key_rotation_period| is 90 days. 121 // 122 // TODO(b/316419439): Change |key_rotation_period| to base::TimeDelta. 123 uint64_t Id(uint64_t project_name_hash, int key_rotation_period); 124 125 // Returns when the key for |project_name_hash| was last rotated, in days 126 // since epoch. Returns nullopt if the key doesn't exist. 127 // 128 // TODO(b/316419439): Change |key_rotation_period| to base::TimeDelta. 129 std::optional<int> LastKeyRotation(uint64_t project_name_hash) const; 130 131 // Return the age of the key for |project_name_hash| since the last rotation, 132 // in weeks. 133 std::optional<int> GetKeyAgeInWeeks(uint64_t project_name_hash) const; 134 135 // Clears all key data. 136 void Purge(); 137 138 private: 139 // Ensure that a valid key exists for |project|. If a key doesn't exist OR if 140 // the key needs to be rotated, then a new key with |key_rotation_period| will 141 // be created. 142 // 143 // This function assumes that |storage_delegate_->IsReady()| is true. 144 void EnsureKeyUpdated(uint64_t project_name_hash, 145 base::TimeDelta key_rotation_period); 146 147 // Retrieves the bytes of the key associated with |project_name_hash|. 148 // If the key does not exist OR if the key is not of size |kKeySize|, returns 149 // std::nullopt . 150 const std::optional<std::string_view> GetKeyBytes( 151 uint64_t project_name_hash) const; 152 153 // Delegate that handles reading and upserting keys. 154 std::unique_ptr<KeyData::StorageDelegate> storage_delegate_; 155 }; 156 157 } // namespace metrics::structured 158 159 #endif // COMPONENTS_METRICS_STRUCTURED_LIB_KEY_DATA_H_ 160