1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
|
// 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_KEY_DATA_H_
#define COMPONENTS_METRICS_STRUCTURED_KEY_DATA_H_
#include <string>
#include "base/files/file_path.h"
#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 "components/metrics/structured/persistent_proto.h"
#include "components/metrics/structured/storage.pb.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
namespace metrics {
namespace structured {
class KeyDataTest;
// 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.xml. This can be used to generate:
// - a user ID for the project with KeyData::Id.
// - a hash of a given value for an event with KeyData::HmacMetric.
//
// 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.
//
// Key storage is backed by a PersistentProto, stored at the path given to the
// constructor.
class KeyData {
public:
KeyData(const base::FilePath& path,
const base::TimeDelta& save_delay,
base::OnceCallback<void()> on_initialized);
~KeyData();
KeyData(const KeyData&) = delete;
KeyData& operator=(const KeyData&) = delete;
// 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.
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.
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.
absl::optional<int> LastKeyRotation(uint64_t project_name_hash);
// Clears all key data from memory and from disk. If this is called before the
// underlying proto has been read from disk, the purge will be performed once
// the read is complete.
void Purge();
// Returns whether this KeyData instance has finished reading from disk and is
// ready to be used. If false, both Id and HmacMetric will return 0u.
bool is_initialized() { return is_initialized_; }
private:
friend class KeyDataTest;
void WriteNowForTest();
void OnRead(ReadStatus status);
void OnWrite(WriteStatus status);
// Ensure that a valid key exists for |project|, and return it. Either returns
// a string of size |kKeySize| or absl::nullopt, which indicates an error. 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.
absl::optional<std::string> ValidateAndGetKey(uint64_t project_name_hash,
int key_rotation_period);
// Regenerate |key|, also updating the |last_key_rotation| and
// |key_rotation_period|. This triggers a save.
void UpdateKey(KeyProto* key, int last_key_rotation, int key_rotation_period);
// Storage for keys.
std::unique_ptr<PersistentProto<KeyDataProto>> proto_;
// Whether this instance has finished reading from disk.
bool is_initialized_ = false;
base::OnceCallback<void()> on_initialized_;
SEQUENCE_CHECKER(sequence_checker_);
scoped_refptr<base::SequencedTaskRunner> task_runner_;
base::WeakPtrFactory<KeyData> weak_factory_{this};
};
} // namespace structured
} // namespace metrics
#endif // COMPONENTS_METRICS_STRUCTURED_KEY_DATA_H_
|