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_KEY_DATA_H_ 6 #define COMPONENTS_METRICS_STRUCTURED_KEY_DATA_H_ 7 8 #include <string> 9 10 #include "base/files/file_path.h" 11 #include "base/memory/scoped_refptr.h" 12 #include "base/memory/weak_ptr.h" 13 #include "base/sequence_checker.h" 14 #include "base/task/sequenced_task_runner.h" 15 #include "components/metrics/structured/persistent_proto.h" 16 #include "components/metrics/structured/storage.pb.h" 17 #include "third_party/abseil-cpp/absl/types/optional.h" 18 19 namespace metrics::structured { 20 21 class KeyDataTest; 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 // KeyData performs key rotation. Every project is associated with a rotation 33 // period, which is 90 days unless specified in structured.xml. Keys are rotated 34 // with a resolution of one day. They are guaranteed not to be used for 35 // HmacMetric or UserProjectId for longer than their rotation period, except in 36 // cases of local clock changes. 37 // 38 // When first created, every project's key rotation date is selected uniformly 39 // so that there is an even distribution of rotations across users. This means 40 // that, for most users, the first rotation period will be shorter than the 41 // standard full rotation period for that project. 42 // 43 // Key storage is backed by a PersistentProto, stored at the path given to the 44 // constructor. 45 class KeyData { 46 public: 47 KeyData(const base::FilePath& path, 48 const base::TimeDelta& save_delay, 49 base::OnceCallback<void()> on_initialized); 50 ~KeyData(); 51 52 KeyData(const KeyData&) = delete; 53 KeyData& operator=(const KeyData&) = delete; 54 55 // Returns a digest of |value| for |metric| in the context of 56 // |project_name_hash|. Terminology: a metric is a (name, value) pair, and an 57 // event is a bundle of metrics. Each event is associated with a project. 58 // 59 // - |project_name_hash| is the uint64 name hash of a project. 60 // - |metric_name_hash| is the uint64 name hash of a metric. 61 // - |value| is the string value to hash. 62 // 63 // The result is the HMAC digest of the |value| salted with |metric|, using 64 // the key for |project_name_hash|. That is: 65 // 66 // HMAC_SHA256(key(project_name_hash), concat(value, hex(event), 67 // hex(metric))) 68 // 69 // Returns 0u in case of an error. 70 uint64_t HmacMetric(uint64_t project_name_hash, 71 uint64_t metric_name_hash, 72 const std::string& value, 73 int key_rotation_period); 74 75 // Returns an ID for this (user, |project_name_hash|) pair. 76 // |project_name_hash| is the name of a project, represented by the first 8 77 // bytes of the MD5 hash of its name defined in structured.xml. 78 // 79 // The derived ID is the first 8 bytes of SHA256(key(project_name_hash)). 80 // Returns 0u in case of an error. 81 // 82 // This ID is intended as the only ID for the events of a particular 83 // structured metrics project. However, events are uploaded from the device 84 // alongside the UMA client ID, which is only removed after the event reaches 85 // the server. This means events are associated with the client ID when 86 // uploaded from the device. See the class comment of 87 // StructuredMetricsProvider for more details. 88 // 89 // Default |key_rotation_period| is 90 days. 90 uint64_t Id(uint64_t project_name_hash, int key_rotation_period); 91 92 // Returns when the key for |project_name_hash| was last rotated, in days 93 // since epoch. Returns nullopt if the key doesn't exist. 94 absl::optional<int> LastKeyRotation(uint64_t project_name_hash) const; 95 96 // Return the age of the key for |project_name_hash| since the last rotation, 97 // in weeks. 98 absl::optional<int> GetKeyAgeInWeeks(uint64_t project_name_hash) const; 99 100 // Clears all key data from memory and from disk. If this is called before the 101 // underlying proto has been read from disk, the purge will be performed once 102 // the read is complete. 103 void Purge(); 104 105 // Returns whether this KeyData instance has finished reading from disk and is 106 // ready to be used. If false, both Id and HmacMetric will return 0u. is_initialized()107 bool is_initialized() { return is_initialized_; } 108 109 private: 110 friend class KeyDataTest; 111 112 void WriteNowForTest(); 113 114 void OnRead(ReadStatus status); 115 116 void OnWrite(WriteStatus status); 117 118 // Ensure that a valid key exists for |project|, and return it. Either returns 119 // a string of size |kKeySize| or absl::nullopt, which indicates an error. If 120 // a key doesn't exist OR if the key needs to be rotated, then a new key with 121 // |key_rotation_period| will be created. 122 absl::optional<std::string> ValidateAndGetKey(uint64_t project_name_hash, 123 int key_rotation_period); 124 125 // Regenerate |key|, also updating the |last_key_rotation| and 126 // |key_rotation_period|. This triggers a save. 127 void UpdateKey(KeyProto* key, int last_key_rotation, int key_rotation_period); 128 129 // Storage for keys. 130 std::unique_ptr<PersistentProto<KeyDataProto>> proto_; 131 132 // Whether this instance has finished reading from disk. 133 bool is_initialized_ = false; 134 135 base::OnceCallback<void()> on_initialized_; 136 137 SEQUENCE_CHECKER(sequence_checker_); 138 139 scoped_refptr<base::SequencedTaskRunner> task_runner_; 140 base::WeakPtrFactory<KeyData> weak_factory_{this}; 141 }; 142 143 } // namespace metrics::structured 144 145 #endif // COMPONENTS_METRICS_STRUCTURED_KEY_DATA_H_ 146