blob: 74a832b14ffc174e6c8f60cad613fcd7a378e704 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CONTENT_BROWSER_AGGREGATION_SERVICE_AGGREGATABLE_REPORT_H_
#define CONTENT_BROWSER_AGGREGATION_SERVICE_AGGREGATABLE_REPORT_H_
#include <stddef.h>
#include <stdint.h>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
#include "base/containers/flat_map.h"
#include "base/containers/span.h"
#include "base/time/time.h"
#include "base/uuid.h"
#include "base/values.h"
#include "content/browser/aggregation_service/public_key.h"
#include "content/common/content_export.h"
#include "third_party/blink/public/mojom/aggregation_service/aggregatable_report.mojom.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace content {
class AggregatableReportRequest;
// The underlying private information which will be sent to the processing
// servers for aggregation. Each payload encodes a single contribution to a
// histogram bucket. This will be encrypted and won't be readable by the
// reporting endpoint.
struct CONTENT_EXPORT AggregationServicePayloadContents {
// TODO(alexmt): Add kDistinctCount option.
enum class Operation {
kHistogram,
};
static constexpr size_t kMaximumFilteringIdMaxBytes = 8;
// The default aggregation coordinator origin will be used if
// `aggregation_coordinator_origin` is `std::nullopt`.
// `max_contributions_allowed` specifies the maximum number of contributions
// per report for use in padding. `filtering_id_bit_size` specifies how many
// bits should be used for the filtering ID encoding; if `std::nullopt`, the
// filtering ID must be omitted from the payload.
AggregationServicePayloadContents(
Operation operation,
std::vector<blink::mojom::AggregatableReportHistogramContribution>
contributions,
blink::mojom::AggregationServiceMode aggregation_mode,
std::optional<url::Origin> aggregation_coordinator_origin,
int max_contributions_allowed,
std::optional<size_t> filtering_id_max_bytes);
AggregationServicePayloadContents(
const AggregationServicePayloadContents& other);
AggregationServicePayloadContents& operator=(
const AggregationServicePayloadContents& other);
AggregationServicePayloadContents(AggregationServicePayloadContents&& other);
AggregationServicePayloadContents& operator=(
AggregationServicePayloadContents&& other);
~AggregationServicePayloadContents();
Operation operation;
std::vector<blink::mojom::AggregatableReportHistogramContribution>
contributions;
blink::mojom::AggregationServiceMode aggregation_mode;
std::optional<url::Origin> aggregation_coordinator_origin;
int max_contributions_allowed;
std::optional<size_t> filtering_id_max_bytes;
};
// Represents the information that will be provided to both the reporting
// endpoint and the processing server(s), i.e. stored in the encrypted payload
// and in the plaintext report.
struct CONTENT_EXPORT AggregatableReportSharedInfo {
enum class DebugMode {
kDisabled,
kEnabled,
};
AggregatableReportSharedInfo(base::Time scheduled_report_time,
base::Uuid report_id,
url::Origin reporting_origin,
DebugMode debug_mode,
base::Value::Dict additional_fields,
std::string api_version,
std::string api_identifier);
AggregatableReportSharedInfo(const AggregatableReportSharedInfo& other) =
delete;
AggregatableReportSharedInfo& operator=(
const AggregatableReportSharedInfo& other) = delete;
AggregatableReportSharedInfo(AggregatableReportSharedInfo&& other);
AggregatableReportSharedInfo& operator=(AggregatableReportSharedInfo&& other);
~AggregatableReportSharedInfo();
// Creates a deep copy of this object.
AggregatableReportSharedInfo Clone() const;
// Serializes to a JSON dictionary, represented as a string.
std::string SerializeAsJson() const;
base::Time scheduled_report_time;
base::Uuid report_id; // Used to prevent double counting.
url::Origin reporting_origin;
DebugMode debug_mode;
base::Value::Dict additional_fields;
std::string api_version;
// Enum string that indicates which API created the report.
std::string api_identifier;
};
// An AggregatableReport contains all the information needed for sending the
// report to its reporting endpoint. All nested information has already been
// serialized and encrypted as necessary.
class CONTENT_EXPORT AggregatableReport {
public:
// This is used to encapsulate the data that is specific to a single
// processing server.
struct CONTENT_EXPORT AggregationServicePayload {
AggregationServicePayload(
std::vector<uint8_t> payload,
std::string key_id,
std::optional<std::vector<uint8_t>> debug_cleartext_payload);
AggregationServicePayload(const AggregationServicePayload& other);
AggregationServicePayload& operator=(
const AggregationServicePayload& other);
AggregationServicePayload(AggregationServicePayload&& other);
AggregationServicePayload& operator=(AggregationServicePayload&& other);
~AggregationServicePayload();
// This payload is constructed using the data in the
// AggregationServicePayloadContents and then encrypted with one of
// `url`'s public keys. For the `kTeeBased` aggregation mode, the plaintext
// of the encrypted payload is a serialized CBOR map structured as follows:
// {
// "operation": "<chosen operation as string>",
// "data": [{
// "bucket": <a 16-byte (i.e. 128-bit) big-endian bytestring>,
// "value": <a 4-byte (i.e. 32-bit) big-endian bytestring>
// }, ...],
// }
// Note that the "data" array may contain multiple contributions.
// For the `kExperimentalPoplar` aggregation mode, the "data" field is
// replaced with:
// "dpf_key": <binary serialization of the DPF key>
std::vector<uint8_t> payload;
// Indicates the chosen encryption key.
std::string key_id;
// If the request's shared info had a `kEnabled` debug_mode, contains the
// cleartext payload for debugging. Otherwise, it is `std::nullopt`.
std::optional<std::vector<uint8_t>> debug_cleartext_payload;
};
// Used to allow mocking `CreateFromRequestAndPublicKeys()` in tests.
class CONTENT_EXPORT Provider {
public:
virtual ~Provider();
// Processes and serializes the information in `report_request` and encrypts
// using the `public_keys` as necessary. The order of `public_keys` should
// correspond to `report_request.processing_urls`, which should be
// sorted. Returns `std::nullopt` if an error occurred during construction.
virtual std::optional<AggregatableReport> CreateFromRequestAndPublicKeys(
const AggregatableReportRequest& report_request,
std::vector<PublicKey> public_keys) const;
// Sets whether to disable encryption of the payload(s). Should only be used
// by the AggregationServiceTool.
static void SetDisableEncryptionForTestingTool(bool should_disable);
private:
static bool g_disable_encryption_for_testing_tool_;
};
// log_2 of the number of buckets
static constexpr size_t kBucketDomainBitLength = 32;
// log_2 of the value output space
static constexpr size_t kValueDomainBitLength = 64;
// Used as a prefix for the authenticated information (i.e. context info).
// This value must not be reused for new protocols or versions of this
// protocol unless the ciphertexts are intended to be compatible. This ensures
// that, even if public keys are reused, the same ciphertext cannot be (i.e.
// no cross-protocol attacks).
static constexpr std::string_view kDomainSeparationPrefix =
"aggregation_service";
AggregatableReport(std::vector<AggregationServicePayload> payloads,
std::string shared_info,
std::optional<uint64_t> debug_key,
base::flat_map<std::string, std::string> additional_fields,
std::optional<url::Origin> aggregation_coordinator_origin);
AggregatableReport(const AggregatableReport& other);
AggregatableReport& operator=(const AggregatableReport& other);
AggregatableReport(AggregatableReport&& other);
AggregatableReport& operator=(AggregatableReport&& other);
~AggregatableReport();
const std::vector<AggregationServicePayload>& payloads() const {
return payloads_;
}
const std::string& shared_info() const { return shared_info_; }
std::optional<uint64_t> debug_key() const { return debug_key_; }
const base::flat_map<std::string, std::string>& additional_fields() const {
return additional_fields_;
}
const std::optional<url::Origin>& aggregation_coordinator_origin() const {
return aggregation_coordinator_origin_;
}
// Returns the JSON representation of this report of the form
// {
// "shared_info": "<shared_info>",
// "aggregation_service_payloads": [
// {
// "payload": "<base64 encoded encrypted data>",
// "key_id": "<string identifying public key used>"
// },
// {
// "payload": "<base64 encoded encrypted data>",
// "key_id": "<string identifying public key used>"
// }
// ]
// }
//
// Where <shared_info> is the serialization of the JSON (with all whitespace
// removed):
// {
// "report_id":"[UUID]",
// "reporting_origin":"https://reporter.example"
// "scheduled_report_time":"[timestamp in seconds]",
// "version":"[api version]",
// }
// Callers may insert API-specific fields into the shared_info dictionary.
// In those cases, the keys are inserted in lexicographic order.
//
// If requested, each "aggregation_service_payloads" element has an extra
// field: `"debug_cleartext_payload": "<base64 encoded payload cleartext>"`.
// Note that APIs may wish to add additional key-value pairs to this returned
// value. Additionally, if requested, the outer JSON will have an extra field:
// `"debug_key": "<unsigned 64-bit integer>"` along with any other extra
// fields specified in `additional_fields_`.
base::Value::Dict GetAsJson() const;
// TODO(crbug.com/40196851): Expose static method to validate that a
// base::Value appears to represent a valid report.
// Returns whether `number` is a valid number of processing URLs for the
// `aggregation_mode`.
static bool IsNumberOfProcessingUrlsValid(
size_t number,
blink::mojom::AggregationServiceMode aggregation_mode);
// Returns whether `number` is a valid number of histogram contributions for
// the `aggregation_mode`.
static bool IsNumberOfHistogramContributionsValid(
size_t number,
blink::mojom::AggregationServiceMode aggregation_mode);
private:
// This vector should have an entry for each processing URL specified in
// the original AggregatableReportRequest. Might be empty for reports created
// for the WebUI if prior to assembly or if assembly failed.
std::vector<AggregationServicePayload> payloads_;
std::string shared_info_;
// Should only be set if the debug mode is enabled (but can still be empty).
// Used as part of the temporary debugging mechanism.
std::optional<uint64_t> debug_key_;
base::flat_map<std::string, std::string> additional_fields_;
std::optional<url::Origin> aggregation_coordinator_origin_;
};
// Represents a request for an AggregatableReport. Contains all the data
// necessary to construct the report except for the PublicKey for each
// processing URL.
class CONTENT_EXPORT AggregatableReportRequest {
public:
// Returns `std::nullopt` if `payload_contents.contributions.size()` is not
// valid for the `payload_contents.aggregation_mode` (see
// `IsNumberOfHistogramContributionsValid()` above). Also returns
// `std::nullopt` if any contribution has a negative value, if
// `shared_info.report_id` is not valid, or if `debug_key.has_value()` but
// `shared_info.debug_mode` is `kDisabled`. Also returns `std::nullopt` if
// `failed_send_attempts` is negative or if
// `payload_contents.max_contributions_allowed` is less than the number of
// contributions. Also returns `std::nullopt` if
// `payload_contents.filtering_id_max_bytes` is non-null and either
// non-positive or greater than `kMaximumFilteringIdMaxBytes`. Also returns
// `std::nullopt` if any contribution's filtering ID does not fit in the given
// `payload_contents.filtering_id_max_bytes`; if the given max bytes is null,
// only null filtering IDs are considered to 'fit', i.e. any non-null value
// will mean this returns `std::nullopt`.
// TODO(alexmt): Add validation for scheduled_report_time being non-null/inf.
static std::optional<AggregatableReportRequest> Create(
AggregationServicePayloadContents payload_contents,
AggregatableReportSharedInfo shared_info,
std::string reporting_path = std::string(),
std::optional<uint64_t> debug_key = std::nullopt,
base::flat_map<std::string, std::string> additional_fields = {},
int failed_send_attempts = 0);
// Returns `std:nullopt` whenever `Create()` would for that condition too.
// Also returns `std::nullopt` if `processing_url.size()` is not valid for the
// `payload_contents.aggregation_mode` (see `IsNumberOfProcessingUrlsValid`).
static std::optional<AggregatableReportRequest> CreateForTesting(
std::vector<GURL> processing_urls,
AggregationServicePayloadContents payload_contents,
AggregatableReportSharedInfo shared_info,
std::string reporting_path = std::string(),
std::optional<uint64_t> debug_key = std::nullopt,
base::flat_map<std::string, std::string> additional_fields = {},
int failed_send_attempts = 0);
// Deserializes a bytestring generated by `Serialize()`. Returns
// `std::nullopt` in the case of a deserialization error.
static std::optional<AggregatableReportRequest> Deserialize(
base::span<const uint8_t> serialized_proto);
// Move-only.
AggregatableReportRequest(AggregatableReportRequest&& other);
AggregatableReportRequest& operator=(AggregatableReportRequest&& other);
~AggregatableReportRequest();
const std::vector<GURL>& processing_urls() const { return processing_urls_; }
const AggregationServicePayloadContents& payload_contents() const {
return payload_contents_;
}
const AggregatableReportSharedInfo& shared_info() const {
return shared_info_;
}
const std::string& reporting_path() const { return reporting_path_; }
std::optional<uint64_t> debug_key() const { return debug_key_; }
const base::flat_map<std::string, std::string>& additional_fields() const {
return additional_fields_;
}
int failed_send_attempts() const { return failed_send_attempts_; }
// Returns the URL this report should be sent to. The return value is invalid
// if the reporting_path is empty.
GURL GetReportingUrl() const;
// Serializes the report request to a binary protobuf encoding. Returns an
// empty vector in case of an error.
std::vector<uint8_t> Serialize();
private:
static std::optional<AggregatableReportRequest> CreateInternal(
std::vector<GURL> processing_urls,
AggregationServicePayloadContents payload_contents,
AggregatableReportSharedInfo shared_info,
std::string reporting_path,
std::optional<uint64_t> debug_key,
base::flat_map<std::string, std::string> additional_fields,
int failed_send_attempts);
AggregatableReportRequest(
std::vector<GURL> processing_urls,
AggregationServicePayloadContents payload_contents,
AggregatableReportSharedInfo shared_info,
std::string reporting_path,
std::optional<uint64_t> debug_key,
base::flat_map<std::string, std::string> additional_fields,
int failed_send_attempts);
std::vector<GURL> processing_urls_;
AggregationServicePayloadContents payload_contents_;
AggregatableReportSharedInfo shared_info_;
// The URL path where the assembled report should be sent (when combined with
// `shared_info_.reporting_origin`). If the `AggregatableReportSender` is not
// being used, this can be left empty.
std::string reporting_path_;
// Can only be set if `shared_info_.debug_mode` is `kEnabled` (but can still
// be empty). Used as part of the temporary debugging mechanism.
std::optional<uint64_t> debug_key_;
base::flat_map<std::string, std::string> additional_fields_;
// Number of times the browser has tried and failed to send this report before
// this attempt. The value in this class is not incremented if this attempt
// fails (until a new object is requested from storage)
int failed_send_attempts_ = 0;
};
CONTENT_EXPORT GURL GetAggregationServiceProcessingUrl(const url::Origin&);
} // namespace content
#endif // CONTENT_BROWSER_AGGREGATION_SERVICE_AGGREGATABLE_REPORT_H_