// Copyright 2019 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef COMPONENTS_METRICS_STRUCTURED_LIB_KEY_DATA_H_ #define COMPONENTS_METRICS_STRUCTURED_LIB_KEY_DATA_H_ #include #include #include #include #include "base/memory/scoped_refptr.h" #include "base/memory/weak_ptr.h" #include "base/sequence_checker.h" #include "base/task/sequenced_task_runner.h" #include "base/time/time.h" #include "components/metrics/structured/lib/persistent_proto.h" #include "components/metrics/structured/lib/proto/key.pb.h" namespace metrics::structured { // KeyData is the central class for managing keys and generating hashes for // structured metrics. // // The class maintains one key and its rotation data for every project defined // in /tools/metrics/structured/sync/structured.xml. This can be used to // generate: // - an ID for the project with KeyData::Id. // - a hash of a given value for an event with KeyData::HmacMetric. // // Every project has a uint64_t project_name_hash that is generated by taking // the first 8 bytes of MD5 hash of the project name. Keys for the project are // retrieved using this project_name_hash. For more details, refer to // //tools/metrics/structured/ccodegen.py. // // KeyData performs key rotation. Every project is associated with a rotation // period, which is 90 days unless specified in structured.xml. Keys are rotated // with a resolution of one day. They are guaranteed not to be used for // HmacMetric or UserProjectId for longer than their rotation period, except in // cases of local clock changes. // // When first created, every project's key rotation date is selected uniformly // so that there is an even distribution of rotations across users. This means // that, for most users, the first rotation period will be shorter than the // standard full rotation period for that project. class KeyData { public: // Delegate to read and upsert keys. class StorageDelegate { public: virtual ~StorageDelegate() = default; // Returns if the delegate is ready to read or upsert keys. virtual bool IsReady() const = 0; // Returns the key associated with |project_name_hash|. // // If the key does not exist yet, then returns nullptr. Note that this will // return the expired key if it needs to be rotated. virtual const KeyProto* GetKey(uint64_t project_name_hash) const = 0; // Upserts the key for |project_name_hash| with duration // |key_rotation_period| and last updated time |last_key_rotation|. // // |last_key_rotation| is the TimeDelta from base::Time::UnixEpoch in // which the key was last rotated. virtual void UpsertKey(uint64_t project_name_hash, base::TimeDelta last_key_rotation, base::TimeDelta key_rotation_period) = 0; // Clears all key data. virtual void Purge() = 0; }; // Key data will use |storage_delegate| to read and upsert keys. explicit KeyData(std::unique_ptr storage_delegate); KeyData(const KeyData&) = delete; KeyData& operator=(const KeyData&) = delete; ~KeyData(); // Returns a digest of |value| for |metric| in the context of // |project_name_hash|. Terminology: a metric is a (name, value) pair, and an // event is a bundle of metrics. Each event is associated with a project. // // - |project_name_hash| is the uint64 name hash of a project. // - |metric_name_hash| is the uint64 name hash of a metric. // - |value| is the string value to hash. // // The result is the HMAC digest of the |value| salted with |metric|, using // the key for |project_name_hash|. That is: // // HMAC_SHA256(key(project_name_hash), concat(value, hex(event), // hex(metric))) // // Returns 0u in case of an error. // // TODO(b/316419439): Change |key_rotation_period| to base::TimeDelta. uint64_t HmacMetric(uint64_t project_name_hash, uint64_t metric_name_hash, const std::string& value, int key_rotation_period); // Returns an ID for this (user, |project_name_hash|) pair. // |project_name_hash| is the name of a project, represented by the first 8 // bytes of the MD5 hash of its name defined in structured.xml. // // The derived ID is the first 8 bytes of SHA256(key(project_name_hash)). // Returns 0u in case of an error. // // This ID is intended as the only ID for the events of a particular // structured metrics project. However, events are uploaded from the device // alongside the UMA client ID, which is only removed after the event reaches // the server. This means events are associated with the client ID when // uploaded from the device. See the class comment of // StructuredMetricsProvider for more details. // // Default |key_rotation_period| is 90 days. // // TODO(b/316419439): Change |key_rotation_period| to base::TimeDelta. uint64_t Id(uint64_t project_name_hash, int key_rotation_period); // Returns when the key for |project_name_hash| was last rotated, in days // since epoch. Returns nullopt if the key doesn't exist. // // TODO(b/316419439): Change |key_rotation_period| to base::TimeDelta. std::optional LastKeyRotation(uint64_t project_name_hash) const; // Return the age of the key for |project_name_hash| since the last rotation, // in weeks. std::optional GetKeyAgeInWeeks(uint64_t project_name_hash) const; // Clears all key data. void Purge(); private: // Ensure that a valid key exists for |project|. If a key doesn't exist OR if // the key needs to be rotated, then a new key with |key_rotation_period| will // be created. // // This function assumes that |storage_delegate_->IsReady()| is true. void EnsureKeyUpdated(uint64_t project_name_hash, base::TimeDelta key_rotation_period); // Retrieves the bytes of the key associated with |project_name_hash|. // If the key does not exist OR if the key is not of size |kKeySize|, returns // std::nullopt . const std::optional GetKeyBytes( uint64_t project_name_hash) const; // Delegate that handles reading and upserting keys. std::unique_ptr storage_delegate_; }; } // namespace metrics::structured #endif // COMPONENTS_METRICS_STRUCTURED_LIB_KEY_DATA_H_