// 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. #include "components/metrics/structured/lib/key_data.h" #include #include #include "base/check.h" #include "base/logging.h" #include "base/notreached.h" #include "base/rand_util.h" #include "base/strings/strcat.h" #include "base/strings/string_number_conversions.h" #include "base/time/time.h" #include "base/unguessable_token.h" #include "components/metrics/structured/lib/histogram_util.h" #include "components/metrics/structured/lib/key_util.h" #include "crypto/hmac.h" #include "crypto/sha2.h" namespace metrics::structured { namespace { std::string HashToHex(const uint64_t hash) { return base::HexEncode(&hash, sizeof(uint64_t)); } int NowInDays() { return (base::Time::Now() - base::Time::UnixEpoch()).InDays(); } } // namespace KeyData::KeyData(std::unique_ptr storage_delegate) : storage_delegate_(std::move(storage_delegate)) { CHECK(storage_delegate_); } KeyData::~KeyData() = default; uint64_t KeyData::Id(const uint64_t project_name_hash, int key_rotation_period) { if (!storage_delegate_->IsReady()) { return 0u; } // Retrieve the key for |project_name_hash|. EnsureKeyUpdated(project_name_hash, base::Days(key_rotation_period)); const std::optional key = GetKeyBytes(project_name_hash); if (!key) { NOTREACHED(); return 0u; } // Compute and return the hash. uint64_t hash; crypto::SHA256HashString(key.value(), &hash, sizeof(uint64_t)); return hash; } uint64_t KeyData::HmacMetric(const uint64_t project_name_hash, const uint64_t metric_name_hash, const std::string& value, int key_rotation_period) { if (!storage_delegate_->IsReady()) { return 0u; } // Retrieve the key for |project_name_hash|. EnsureKeyUpdated(project_name_hash, base::Days(key_rotation_period)); const std::optional key = GetKeyBytes(project_name_hash); if (!key) { NOTREACHED(); return 0u; } // Initialize the HMAC. crypto::HMAC hmac(crypto::HMAC::HashAlgorithm::SHA256); CHECK(hmac.Init(key.value())); // Compute and return the digest. const std::string salted_value = base::StrCat({HashToHex(metric_name_hash), value}); uint64_t digest; CHECK(hmac.Sign(salted_value, reinterpret_cast(&digest), sizeof(digest))); return digest; } std::optional KeyData::LastKeyRotation( const uint64_t project_name_hash) const { const KeyProto* key = storage_delegate_->GetKey(project_name_hash); if (!key) { return std::nullopt; } return key->last_rotation(); } std::optional KeyData::GetKeyAgeInWeeks(uint64_t project_name_hash) const { std::optional last_rotation = LastKeyRotation(project_name_hash); if (!last_rotation.has_value()) { return std::nullopt; } const int now = NowInDays(); const int days_since_rotation = now - *last_rotation; return days_since_rotation / 7; } void KeyData::Purge() { storage_delegate_->Purge(); } void KeyData::EnsureKeyUpdated(const uint64_t project_name_hash, base::TimeDelta key_rotation_period) { CHECK(storage_delegate_->IsReady()); const int now = NowInDays(); const int key_rotation_period_days = key_rotation_period.InDays(); const KeyProto* key = storage_delegate_->GetKey(project_name_hash); // Generate or rotate key. if (!key || key->last_rotation() == 0) { LogKeyValidation(KeyValidationState::kCreated); // If the key does not exist, generate a new one. Set the last rotation to a // uniformly selected day between today and |key_rotation_period| days // ago, to uniformly distribute users amongst rotation cohorts. const int rotation_seed = base::RandInt(0, key_rotation_period_days - 1); storage_delegate_->UpsertKey(project_name_hash, base::Days(now - rotation_seed), key_rotation_period); } else if (now - key->last_rotation() > key_rotation_period_days) { LogKeyValidation(KeyValidationState::kRotated); // If the key is outdated, generate a new one. Update the last rotation // such that the user stays in the same cohort. // // Note that if the max key rotation period has changed, the new rotation // period will be used to calculate whether the key should be rotated or // not. const int new_last_rotation = now - (now - key->last_rotation()) % key_rotation_period_days; storage_delegate_->UpsertKey( project_name_hash, base::Days(new_last_rotation), key_rotation_period); } else { LogKeyValidation(KeyValidationState::kValid); } } const std::optional KeyData::GetKeyBytes( const uint64_t project_name_hash) const { // Re-fetch the key after keys are rotated. const KeyProto* key = storage_delegate_->GetKey(project_name_hash); if (!key) { return std::nullopt; } // Return the key unless it's the wrong size, in which case return nullopt. const std::string_view key_string = key->key(); if (key_string.size() != kKeySize) { return std::nullopt; } return key_string; } } // namespace metrics::structured