14408: Drop PROCHLO.
Fixes https://github.com/brave/brave-browser/issues/14408
This commit is contained in:
@@ -1,25 +1,8 @@
|
||||
import("//third_party/protobuf/proto_library.gni")
|
||||
|
||||
static_library("brave_prochlo") {
|
||||
sources = [
|
||||
"brave_prochlo_crypto.cc",
|
||||
"brave_prochlo_crypto.h",
|
||||
"brave_prochlo_message.cc",
|
||||
"brave_prochlo_message.h",
|
||||
"prochlo_crypto.cc",
|
||||
"prochlo_crypto.h",
|
||||
"prochlo_data.h",
|
||||
]
|
||||
|
||||
public_deps = [
|
||||
":prochlo_proto",
|
||||
"//base:base",
|
||||
"//crypto",
|
||||
"//third_party/boringssl",
|
||||
"//third_party/protobuf:protobuf_lite",
|
||||
]
|
||||
}
|
||||
|
||||
# This component is a leftover that we will cleanup after transition
|
||||
# to the new p3a ping format.
|
||||
# https://github.com/brave/brave-browser/issues/15967
|
||||
proto_library("prochlo_proto") {
|
||||
sources = [ "prochlo_message.proto" ]
|
||||
proto_in_dir = "."
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
include_rules = [
|
||||
"+third_party/boringssl",
|
||||
]
|
||||
@@ -1,4 +0,0 @@
|
||||
Name: PROCHLO
|
||||
URL: https://github.com/google/prochlo
|
||||
License: Apache-2.0
|
||||
License File: /brave/common/licenses/Apache-2.0
|
||||
@@ -1,36 +0,0 @@
|
||||
/* Copyright 2019 The Brave Authors. All rights reserved.
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "brave/components/brave_prochlo/brave_prochlo_crypto.h"
|
||||
|
||||
#include "third_party/boringssl/src/include/openssl/pem.h"
|
||||
|
||||
namespace prochlo {
|
||||
|
||||
BraveProchloCrypto::BraveProchloCrypto() = default;
|
||||
|
||||
bool BraveProchloCrypto::load_analyzer_key_from_bytes(
|
||||
const std::vector<char>& bytes) {
|
||||
public_analyzer_key_ = load_public_key_from_bytes(bytes);
|
||||
return public_analyzer_key_ != nullptr;
|
||||
}
|
||||
|
||||
bool BraveProchloCrypto::load_shuffler_key_from_bytes(
|
||||
const std::vector<char>& bytes) {
|
||||
public_shuffler_key_ = load_public_key_from_bytes(bytes);
|
||||
return public_shuffler_key_ != nullptr;
|
||||
}
|
||||
|
||||
EVP_PKEY* BraveProchloCrypto::load_public_key_from_bytes(
|
||||
const std::vector<char>& bytes) {
|
||||
bssl::UniquePtr<BIO> bio(
|
||||
BIO_new_mem_buf(bytes.data(), static_cast<int>(bytes.size())));
|
||||
if (!bio) {
|
||||
return nullptr;
|
||||
}
|
||||
return PEM_read_bio_PUBKEY(bio.get(), nullptr, nullptr, nullptr);
|
||||
}
|
||||
|
||||
} // namespace prochlo
|
||||
@@ -1,35 +0,0 @@
|
||||
/* Copyright 2019 The Brave Authors. All rights reserved.
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef BRAVE_COMPONENTS_BRAVE_PROCHLO_BRAVE_PROCHLO_CRYPTO_H_
|
||||
#define BRAVE_COMPONENTS_BRAVE_PROCHLO_BRAVE_PROCHLO_CRYPTO_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "base/macros.h"
|
||||
#include "brave/components/brave_prochlo/prochlo_crypto.h"
|
||||
|
||||
namespace prochlo {
|
||||
|
||||
class BraveProchloCrypto : public Crypto {
|
||||
public:
|
||||
BraveProchloCrypto();
|
||||
|
||||
// Load the public key for the Analyzer from bytes.
|
||||
bool load_analyzer_key_from_bytes(const std::vector<char>& bytes);
|
||||
|
||||
// Load the public key for the Analyzer from bytes.
|
||||
bool load_shuffler_key_from_bytes(const std::vector<char>& bytes);
|
||||
|
||||
private:
|
||||
// Load a public key returning the structure.
|
||||
EVP_PKEY* load_public_key_from_bytes(const std::vector<char>& bytes);
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(BraveProchloCrypto);
|
||||
};
|
||||
|
||||
} // namespace prochlo
|
||||
|
||||
#endif // BRAVE_COMPONENTS_BRAVE_PROCHLO_BRAVE_PROCHLO_CRYPTO_H_
|
||||
@@ -1,260 +0,0 @@
|
||||
/* Copyright 2019 The Brave Authors. All rights reserved.
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "brave/components/brave_prochlo/brave_prochlo_message.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "base/containers/flat_set.h"
|
||||
#include "base/cxx17_backports.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/strings/string_number_conversions.h"
|
||||
#include "base/trace_event/trace_event.h"
|
||||
#include "brave/components/brave_prochlo/brave_prochlo_crypto.h"
|
||||
#include "brave/components/brave_prochlo/prochlo_data.h"
|
||||
#include "brave/components/brave_prochlo/prochlo_message.pb.h"
|
||||
#include "crypto/sha2.h"
|
||||
|
||||
namespace prochlo {
|
||||
|
||||
namespace {
|
||||
|
||||
// TODO(iefremov): Make it possible to use testing keys.
|
||||
// TODO(iefremov): Key versioning?
|
||||
constexpr char kShufflerKey[] = R"(
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEB+tJ1w8nSnusfxfXV1pq+teKmsb+
|
||||
kH5op6DjhJABBiLWDhTXyLB38noi7BMwNC3fAcrlVAYPj4ejQ8ohHuSSRA==
|
||||
-----END PUBLIC KEY-----)";
|
||||
|
||||
constexpr char kAnalyzerKey[] = R"(
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEQCeVJbcADloHb8bwftIi1UO0smiz
|
||||
8ObdAFQ8j3U9cMehGqI3zXgS8APvBW/9XxMkb4XWQe+t9h6qHq82P6zcBg==
|
||||
-----END PUBLIC KEY-----)";
|
||||
|
||||
bool MakeProchlomation(uint64_t metric,
|
||||
const uint8_t* data,
|
||||
const uint8_t* crowd_id,
|
||||
ShufflerItem* shuffler_item) {
|
||||
DCHECK(data);
|
||||
DCHECK(crowd_id);
|
||||
DCHECK(shuffler_item);
|
||||
// TODO(iefremov): - create patch for adding `brave_p3a`
|
||||
// to src/base/trace_event/builtin_categories.h
|
||||
// TRACE_EVENT0("brave_p3a", "MakeProchlomation");
|
||||
|
||||
BraveProchloCrypto crypto;
|
||||
|
||||
const std::vector<char> shuffler_key(
|
||||
&kShufflerKey[0], &kShufflerKey[0] + base::size(kShufflerKey));
|
||||
if (!crypto.load_shuffler_key_from_bytes(shuffler_key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::vector<char> analyzer_key(
|
||||
&kAnalyzerKey[0], &kAnalyzerKey[0] + base::size(kAnalyzerKey));
|
||||
if (!crypto.load_analyzer_key_from_bytes(analyzer_key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// We have to create a Prochlomation and a PlainShufflerItem to encrypt them
|
||||
// both into an AnalyzerItea and a ShufflerItem, respectively. We'll stage
|
||||
// those here. We can probably do this more efficiently to avoid copies.
|
||||
Prochlomation prochlomation;
|
||||
PlainShufflerItem plain_shuffler_item;
|
||||
|
||||
// First the prochlomation
|
||||
prochlomation.metric = metric;
|
||||
memcpy(prochlomation.data, data, kProchlomationDataLength);
|
||||
|
||||
// Then the AnalyzerItem of the PlainShufflerItem
|
||||
if (!crypto.EncryptForAnalyzer(prochlomation,
|
||||
&plain_shuffler_item.analyzer_item)) {
|
||||
NOTREACHED();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Now prepare the PlainShufflerItem
|
||||
memcpy(plain_shuffler_item.crowd_id, crowd_id, kCrowdIdLength);
|
||||
|
||||
// And create the ShufflerItem
|
||||
if (!crypto.EncryptForShuffler(plain_shuffler_item, shuffler_item)) {
|
||||
NOTREACHED();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void InitProchloMessage(uint64_t metric_hash,
|
||||
const ShufflerItem& item,
|
||||
brave_pyxis::PyxisMessage* pyxis_message) {
|
||||
DCHECK(pyxis_message);
|
||||
brave_pyxis::PyxisValue* value = pyxis_message->add_pyxis_values();
|
||||
value->set_ciphertext(item.ciphertext, kPlainShufflerItemLength);
|
||||
value->set_tag(item.tag, kTagLength);
|
||||
value->set_nonce(item.nonce, kNonceLength);
|
||||
value->set_metric_id(metric_hash);
|
||||
value->set_client_public_key(item.client_public_key, kPublicKeyLength);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
MessageMetainfo::MessageMetainfo() = default;
|
||||
MessageMetainfo::~MessageMetainfo() = default;
|
||||
|
||||
void GenerateProchloMessage(uint64_t metric_hash,
|
||||
uint64_t metric_value,
|
||||
const MessageMetainfo& meta,
|
||||
brave_pyxis::PyxisMessage* pyxis_message) {
|
||||
// TODO(iefremov): - create patch for adding `brave_p3a`
|
||||
// to src/base/trace_event/builtin_categories.h
|
||||
// TRACE_EVENT0("brave_p3a", "GenerateProchloMessage");
|
||||
ShufflerItem item;
|
||||
uint8_t data[kProchlomationDataLength] = {0};
|
||||
uint8_t crowd_id[kCrowdIdLength] = {0};
|
||||
|
||||
// First byte contains the 4 booleans.
|
||||
const char daily = 1;
|
||||
const char weekly = 0;
|
||||
const char monthly = 2;
|
||||
const char first = 0;
|
||||
data[0] = daily | weekly | monthly | first;
|
||||
uint8_t* ptr = data;
|
||||
ptr++;
|
||||
|
||||
// Find out years of install and survey.
|
||||
base::Time::Exploded exploded;
|
||||
meta.date_of_survey.LocalExplode(&exploded);
|
||||
DCHECK_GE(exploded.year, 999);
|
||||
const std::string yos = base::NumberToString(exploded.year).substr(2, 4);
|
||||
meta.date_of_install.LocalExplode(&exploded);
|
||||
DCHECK_GE(exploded.year, 999);
|
||||
const std::string yoi = base::NumberToString(exploded.year).substr(2, 4);
|
||||
|
||||
const std::string metastring =
|
||||
"," + meta.country_code + "," + meta.platform + "," + meta.version + "," +
|
||||
meta.channel + "," + yoi + base::NumberToString(meta.woi) + "," + yos +
|
||||
base::NumberToString(meta.wos) + "," + meta.refcode + ",";
|
||||
|
||||
const std::string metric_value_str = base::NumberToString(metric_value);
|
||||
|
||||
// TODO(iefremov): replace with 'if'?
|
||||
CHECK_LE(metastring.size() + metric_value_str.size(),
|
||||
kProchlomationDataLength - 1);
|
||||
|
||||
memcpy(ptr, metastring.data(), metastring.size());
|
||||
ptr += metastring.size();
|
||||
memcpy(ptr, metric_value_str.data(), metric_value_str.size());
|
||||
|
||||
// TODO(iefremov): Salt?
|
||||
crypto::SHA256HashString(
|
||||
base::NumberToString(metric_hash) + base::NumberToString(metric_value),
|
||||
crowd_id, kCrowdIdLength);
|
||||
MakeProchlomation(metric_hash, data, crowd_id, &item);
|
||||
|
||||
InitProchloMessage(metric_hash, item, pyxis_message);
|
||||
}
|
||||
|
||||
void GenerateP3AMessage(uint64_t metric_hash,
|
||||
uint64_t metric_value,
|
||||
const MessageMetainfo& meta,
|
||||
brave_pyxis::RawP3AValue* p3a_message) {
|
||||
// TODO(iefremov): - create patch for adding `brave_p3a`
|
||||
// to src/base/trace_event/builtin_categories.h
|
||||
// TRACE_EVENT0("brave_p3a", "GenerateP3AMessage");
|
||||
uint8_t data[kProchlomationDataLength] = {0};
|
||||
|
||||
// First byte contains the 4 booleans.
|
||||
const char daily = 1;
|
||||
const char weekly = 0;
|
||||
const char monthly = 2;
|
||||
const char first = 0;
|
||||
data[0] = daily | weekly | monthly | first;
|
||||
uint8_t* ptr = data;
|
||||
ptr++;
|
||||
|
||||
// Find out years of install and survey.
|
||||
base::Time::Exploded exploded;
|
||||
meta.date_of_survey.LocalExplode(&exploded);
|
||||
DCHECK_GE(exploded.year, 999);
|
||||
const std::string yos = base::NumberToString(exploded.year).substr(2, 4);
|
||||
meta.date_of_install.LocalExplode(&exploded);
|
||||
DCHECK_GE(exploded.year, 999);
|
||||
const std::string yoi = base::NumberToString(exploded.year).substr(2, 4);
|
||||
|
||||
const std::string metastring =
|
||||
"," + meta.country_code + "," + meta.platform + "," + meta.version + "," +
|
||||
meta.channel + "," + yoi + base::NumberToString(meta.woi) + "," + yos +
|
||||
base::NumberToString(meta.wos) + "," + meta.refcode + ",";
|
||||
|
||||
const std::string metric_value_str = base::NumberToString(metric_value);
|
||||
|
||||
// TODO(iefremov): replace with 'if'?
|
||||
CHECK_LE(metastring.size() + metric_value_str.size(),
|
||||
kProchlomationDataLength - 1);
|
||||
|
||||
memcpy(ptr, metastring.data(), metastring.size());
|
||||
ptr += metastring.size();
|
||||
memcpy(ptr, metric_value_str.data(), metric_value_str.size());
|
||||
|
||||
// Init the message.
|
||||
p3a_message->set_metric_id(metric_hash);
|
||||
p3a_message->set_p3a_info(data, kProchlomationDataLength);
|
||||
}
|
||||
|
||||
void MaybeStripRefcodeAndCountry(prochlo::MessageMetainfo* meta) {
|
||||
const std::string& refcode = meta->refcode;
|
||||
const std::string& country = meta->country_code;
|
||||
constexpr char kRefcodeNone[] = "none";
|
||||
constexpr char kCountryOther[] = "other";
|
||||
constexpr char kRefcodeOther[] = "other";
|
||||
|
||||
static base::flat_set<std::string> const kLinuxCountries(
|
||||
{"US", "FR", "DE", "GB", "IN", "BR", "PL", "NL", "ES", "CA", "IT", "AU",
|
||||
"MX", "CH", "RU", "ZA", "SE", "BE", "JP"});
|
||||
|
||||
static base::flat_set<std::string> const kNotableRefcodes(
|
||||
{"BRV001", "GDB255", "APP709", "GBW423", "BRT001", "VNI569", "ICO964",
|
||||
"ILY758"});
|
||||
|
||||
static base::flat_set<std::string> const kNotableCountries(
|
||||
{"FR", "PH", "GB", "IN", "DE", "BR", "CA", "IT", "ES", "NL", "MX", "AU",
|
||||
"RU", "JP", "PL", "ID", "KR", "AR"});
|
||||
|
||||
DCHECK(meta);
|
||||
if (meta->platform == "linux-bc") {
|
||||
// Because Linux has no refcodes, ignore, and if we have more than
|
||||
// 3/0.05 = 60 users in a country for a week of install, we can send
|
||||
// country.
|
||||
meta->refcode = kRefcodeNone;
|
||||
if (kLinuxCountries.count(country) == 0) {
|
||||
meta->country_code = kCountryOther;
|
||||
}
|
||||
} else {
|
||||
// Now the minimum platform is MacOS at ~3%, so cut off for a group under
|
||||
// here becomes 3/(0.05*0.03) = 2000.
|
||||
if (country == "US" || country.empty()) {
|
||||
const bool us_and_ref =
|
||||
country == "US" && (refcode == kRefcodeNone || refcode == "GDB255" ||
|
||||
refcode == "BRV001");
|
||||
const bool unknown_and_ref =
|
||||
country.empty() &&
|
||||
(kNotableRefcodes.count(refcode) > 0 || refcode == kRefcodeNone);
|
||||
|
||||
if (!(us_and_ref || unknown_and_ref)) {
|
||||
meta->refcode = kRefcodeOther;
|
||||
}
|
||||
} else if (kNotableCountries.count(country) > 0) {
|
||||
meta->refcode = kRefcodeOther;
|
||||
} else {
|
||||
meta->country_code = kCountryOther;
|
||||
meta->refcode = kRefcodeOther;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace prochlo
|
||||
@@ -1,516 +0,0 @@
|
||||
// Copyright 2017 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <assert.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include "third_party/boringssl/src/include/openssl/ecdh.h"
|
||||
#include "third_party/boringssl/src/include/openssl/err.h"
|
||||
#include "third_party/boringssl/src/include/openssl/hmac.h"
|
||||
#include "third_party/boringssl/src/include/openssl/pem.h"
|
||||
#include "third_party/boringssl/src/include/openssl/rand.h"
|
||||
|
||||
#include "brave/components/brave_prochlo/prochlo_crypto.h"
|
||||
|
||||
namespace prochlo {
|
||||
|
||||
bool Crypto::load_analyzer_key(const std::string& keyfile) {
|
||||
public_analyzer_key_ = load_public_key(keyfile);
|
||||
return public_analyzer_key_ != nullptr;
|
||||
}
|
||||
|
||||
bool Crypto::load_shuffler_key(const std::string& keyfile) {
|
||||
public_shuffler_key_ = load_public_key(keyfile);
|
||||
return public_shuffler_key_ != nullptr;
|
||||
}
|
||||
|
||||
EVP_PKEY* Crypto::load_public_key(const std::string& keyfile) {
|
||||
FILE* fp = fopen(keyfile.c_str(), "r");
|
||||
if (fp == nullptr) {
|
||||
// warn("fopen()");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
EVP_PKEY* key;
|
||||
key = PEM_read_PUBKEY(fp, NULL, NULL, NULL);
|
||||
fclose(fp);
|
||||
|
||||
if (key == nullptr) {
|
||||
ERR_print_errors_fp(stderr);
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
Crypto::Crypto()
|
||||
: public_shuffler_key_(nullptr), public_analyzer_key_(nullptr) {
|
||||
// Pedantically check that we have the same endianness everywhere
|
||||
// uint32_t number = 1;
|
||||
// assert(reinterpret_cast<uint8_t*>(&number)[0] == 1);
|
||||
|
||||
// Ensure the AES128-GCM default nonce length is kNonceLength
|
||||
assert(EVP_CIPHER_iv_length(EVP_aes_128_gcm()) == kNonceLength);
|
||||
}
|
||||
|
||||
Crypto::~Crypto() {
|
||||
if (public_shuffler_key_ != nullptr) {
|
||||
EVP_PKEY_free(public_shuffler_key_);
|
||||
}
|
||||
if (public_analyzer_key_ != nullptr) {
|
||||
EVP_PKEY_free(public_analyzer_key_);
|
||||
}
|
||||
}
|
||||
|
||||
Crypto::ProchlomationToAnalyzerItemEncryption::
|
||||
ProchlomationToAnalyzerItemEncryption(EVP_PKEY* peer_key,
|
||||
const Prochlomation& prochlomation,
|
||||
AnalyzerItem* analyzer_item)
|
||||
: Encryption(peer_key),
|
||||
prochlomation(prochlomation),
|
||||
analyzer_item(analyzer_item) {}
|
||||
|
||||
uint8_t* Crypto::ProchlomationToAnalyzerItemEncryption::ToPublicKey() {
|
||||
return analyzer_item->client_public_key;
|
||||
}
|
||||
|
||||
const char* Crypto::ProchlomationToAnalyzerItemEncryption::TypeString() {
|
||||
return "Prochlomation->AnalyzerItem";
|
||||
}
|
||||
|
||||
uint8_t* Crypto::ProchlomationToAnalyzerItemEncryption::ToNonce() {
|
||||
return analyzer_item->nonce;
|
||||
}
|
||||
|
||||
uint8_t* Crypto::ProchlomationToAnalyzerItemEncryption::ToTag() {
|
||||
return analyzer_item->tag;
|
||||
}
|
||||
|
||||
bool Crypto::ProchlomationToAnalyzerItemEncryption::StreamDataForEncryption(
|
||||
EVP_CIPHER_CTX* ctx) {
|
||||
// Stream the proclomation to the cipher and write out the ciphertext.
|
||||
uint8_t* next_byte = nullptr;
|
||||
size_t ciphertext_byte_count = 0;
|
||||
int32_t out_length;
|
||||
|
||||
// First the metric
|
||||
next_byte = &analyzer_item->ciphertext[ciphertext_byte_count];
|
||||
const uint8_t* to_metric =
|
||||
reinterpret_cast<const uint8_t*>(&prochlomation.metric);
|
||||
if (EVP_EncryptUpdate(ctx, next_byte, &out_length, to_metric,
|
||||
sizeof(prochlomation.metric)) != 1) {
|
||||
// warn("Couldn't encrypt metric with AES128-GCM.");
|
||||
ERR_print_errors_fp(stderr);
|
||||
return false;
|
||||
}
|
||||
ciphertext_byte_count += out_length;
|
||||
|
||||
// And the data
|
||||
next_byte = &analyzer_item->ciphertext[ciphertext_byte_count];
|
||||
const uint8_t* to_data = prochlomation.data;
|
||||
if (EVP_EncryptUpdate(ctx, next_byte, &out_length, to_data,
|
||||
kProchlomationDataLength) != 1) {
|
||||
// warn("Couldn't encrypt data with AES128-GCM.");
|
||||
ERR_print_errors_fp(stderr);
|
||||
return false;
|
||||
}
|
||||
ciphertext_byte_count += out_length;
|
||||
assert(ciphertext_byte_count == kProchlomationCiphertextLength);
|
||||
return true;
|
||||
}
|
||||
|
||||
Crypto::PlainShufflerItemToShufflerItemEncryption::
|
||||
PlainShufflerItemToShufflerItemEncryption(
|
||||
EVP_PKEY* peer_key,
|
||||
const PlainShufflerItem& plain_shuffler_item,
|
||||
ShufflerItem* shuffler_item)
|
||||
: Encryption(peer_key),
|
||||
plain_shuffler_item(plain_shuffler_item),
|
||||
shuffler_item(shuffler_item) {}
|
||||
|
||||
uint8_t* Crypto::PlainShufflerItemToShufflerItemEncryption::ToPublicKey() {
|
||||
return shuffler_item->client_public_key;
|
||||
}
|
||||
|
||||
const char* Crypto::PlainShufflerItemToShufflerItemEncryption::TypeString() {
|
||||
return "PlainShufflerItem->ShufflerItem";
|
||||
}
|
||||
|
||||
uint8_t* Crypto::PlainShufflerItemToShufflerItemEncryption::ToNonce() {
|
||||
return shuffler_item->nonce;
|
||||
}
|
||||
|
||||
uint8_t* Crypto::PlainShufflerItemToShufflerItemEncryption::ToTag() {
|
||||
return shuffler_item->tag;
|
||||
}
|
||||
|
||||
bool Crypto::PlainShufflerItemToShufflerItemEncryption::StreamDataForEncryption(
|
||||
EVP_CIPHER_CTX* ctx) {
|
||||
// Stream the PlainShufflerItem to the CIPHER and write out the ciphertext.
|
||||
uint8_t* next_byte = nullptr;
|
||||
size_t ciphertext_byte_count = 0;
|
||||
int32_t out_length;
|
||||
|
||||
// First the analyzer item (i.e., its innards).
|
||||
next_byte = &shuffler_item->ciphertext[ciphertext_byte_count];
|
||||
const uint8_t* to_analyzer_item_ciphertext =
|
||||
plain_shuffler_item.analyzer_item.ciphertext;
|
||||
if (EVP_EncryptUpdate(ctx, next_byte, &out_length,
|
||||
to_analyzer_item_ciphertext,
|
||||
kProchlomationCiphertextLength) != 1) {
|
||||
// warn("Couldn't encrypt analyzer item ciphertext with AES128-GCM.");
|
||||
ERR_print_errors_fp(stderr);
|
||||
return false;
|
||||
}
|
||||
ciphertext_byte_count += out_length;
|
||||
|
||||
next_byte = &shuffler_item->ciphertext[ciphertext_byte_count];
|
||||
const uint8_t* to_analyzer_item_tag = plain_shuffler_item.analyzer_item.tag;
|
||||
if (EVP_EncryptUpdate(ctx, next_byte, &out_length, to_analyzer_item_tag,
|
||||
kTagLength) != 1) {
|
||||
// warn("Couldn't encrypt analyzer item tag with AES128-GCM.");
|
||||
ERR_print_errors_fp(stderr);
|
||||
return false;
|
||||
}
|
||||
ciphertext_byte_count += out_length;
|
||||
|
||||
next_byte = &shuffler_item->ciphertext[ciphertext_byte_count];
|
||||
const uint8_t* to_analyzer_item_nonce =
|
||||
plain_shuffler_item.analyzer_item.nonce;
|
||||
if (EVP_EncryptUpdate(ctx, next_byte, &out_length, to_analyzer_item_nonce,
|
||||
kNonceLength) != 1) {
|
||||
// warn("Couldn't encrypt analyzer item nonce with AES128-GCM.");
|
||||
ERR_print_errors_fp(stderr);
|
||||
return false;
|
||||
}
|
||||
ciphertext_byte_count += out_length;
|
||||
|
||||
next_byte = &shuffler_item->ciphertext[ciphertext_byte_count];
|
||||
const uint8_t* to_analyzer_item_client_public_key =
|
||||
plain_shuffler_item.analyzer_item.client_public_key;
|
||||
if (EVP_EncryptUpdate(ctx, next_byte, &out_length,
|
||||
to_analyzer_item_client_public_key,
|
||||
kPublicKeyLength) != 1) {
|
||||
// warn("Couldn't encrypt analyzer item client public key with
|
||||
// AES128-GCM.");
|
||||
ERR_print_errors_fp(stderr);
|
||||
return false;
|
||||
}
|
||||
ciphertext_byte_count += out_length;
|
||||
|
||||
// And now finish with the crowd ID
|
||||
next_byte = &shuffler_item->ciphertext[ciphertext_byte_count];
|
||||
const uint8_t* to_crowd_id = plain_shuffler_item.crowd_id;
|
||||
if (EVP_EncryptUpdate(ctx, next_byte, &out_length, to_crowd_id,
|
||||
kCrowdIdLength) != 1) {
|
||||
// warn("Couldn't encrypt crowd IDwith AES128-GCM.");
|
||||
ERR_print_errors_fp(stderr);
|
||||
return false;
|
||||
}
|
||||
ciphertext_byte_count += out_length;
|
||||
|
||||
assert(ciphertext_byte_count == kPlainShufflerItemLength);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Crypto::MakeEncryptedMessage(Encryption* encryption) {
|
||||
EVP_PKEY* my_key = nullptr;
|
||||
EVP_PKEY* peer_key = encryption->ToPeerKey();
|
||||
|
||||
do { // Using BoringSSL's scoped EVP_PKEY pointers would be a lot more
|
||||
// exciting here that the do {} while(false) kludge.
|
||||
if (!GenerateKeyPair(peer_key, &my_key, encryption->ToPublicKey())) {
|
||||
// warn("Couldn't generate an ephemeral keypair during %s message
|
||||
// creation.",
|
||||
// encryption->TypeString());
|
||||
break;
|
||||
}
|
||||
|
||||
uint8_t symmetric_key[kSymmetricKeyLength];
|
||||
if (!DeriveSecretSymmetricKey(my_key, peer_key, symmetric_key)) {
|
||||
// warn("Couldn't generate a symmetric key during %s message creation.",
|
||||
// encryption->TypeString());
|
||||
break;
|
||||
}
|
||||
|
||||
if (!Encrypt(symmetric_key, encryption)) {
|
||||
// warn("Couldn't encrypt for %s.", encryption->TypeString());
|
||||
break;
|
||||
}
|
||||
|
||||
if (my_key != nullptr) {
|
||||
EVP_PKEY_free(my_key);
|
||||
}
|
||||
return true;
|
||||
} while (false);
|
||||
|
||||
if (my_key != nullptr) {
|
||||
EVP_PKEY_free(my_key);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Crypto::EncryptForAnalyzer(const Prochlomation& prochlomation,
|
||||
AnalyzerItem* analyzer_item) {
|
||||
ProchlomationToAnalyzerItemEncryption encryption(
|
||||
public_analyzer_key_, prochlomation, analyzer_item);
|
||||
return MakeEncryptedMessage(&encryption);
|
||||
}
|
||||
|
||||
bool Crypto::EncryptForShuffler(const PlainShufflerItem& plain_shuffler_item,
|
||||
ShufflerItem* shuffler_item) {
|
||||
PlainShufflerItemToShufflerItemEncryption encryption(
|
||||
public_shuffler_key_, plain_shuffler_item, shuffler_item);
|
||||
return MakeEncryptedMessage(&encryption);
|
||||
}
|
||||
|
||||
bool Crypto::GenerateKeyPair(EVP_PKEY* peer_public_key,
|
||||
EVP_PKEY** key_out,
|
||||
uint8_t* binary_key) {
|
||||
assert(peer_public_key != nullptr);
|
||||
assert(key_out != nullptr);
|
||||
assert(binary_key != nullptr);
|
||||
assert(*key_out == nullptr);
|
||||
|
||||
EVP_PKEY_CTX* ctx = nullptr;
|
||||
EVP_PKEY* key = nullptr;
|
||||
BIO* bio = NULL;
|
||||
|
||||
do {
|
||||
// Generate a key based on the peer's key parameters.
|
||||
ctx = EVP_PKEY_CTX_new(peer_public_key, /*e=*/nullptr);
|
||||
if (ctx == nullptr) {
|
||||
// warn("Couldn't create an EVP_PKEY_CTX.");
|
||||
ERR_print_errors_fp(stderr);
|
||||
break;
|
||||
}
|
||||
|
||||
if (EVP_PKEY_keygen_init(ctx) != 1) {
|
||||
// warn("Couldn't initialize the key-pair generation.");
|
||||
ERR_print_errors_fp(stderr);
|
||||
break;
|
||||
}
|
||||
|
||||
if (EVP_PKEY_keygen(ctx, &key) != 1) {
|
||||
// warn("Couldn't generate a key pair.");
|
||||
ERR_print_errors_fp(stderr);
|
||||
break;
|
||||
}
|
||||
|
||||
// Serialize the key.
|
||||
bio = BIO_new(BIO_s_mem());
|
||||
if (bio == nullptr) {
|
||||
// warn("Couldn't allocate an OpenSSL buffer.");
|
||||
ERR_print_errors_fp(stderr);
|
||||
break;
|
||||
}
|
||||
|
||||
if (i2d_PUBKEY_bio(bio, key) != 1) {
|
||||
// warn("Couldn't serialize a key pair.");
|
||||
ERR_print_errors_fp(stderr);
|
||||
break;
|
||||
}
|
||||
|
||||
uint8_t* serialized_buffer = nullptr;
|
||||
size_t serialized_key_length =
|
||||
BIO_get_mem_data(bio, reinterpret_cast<char**>(&serialized_buffer));
|
||||
// We'd better have provisioned enough space for the serialized public key.
|
||||
assert(serialized_key_length <= kPublicKeyLength);
|
||||
|
||||
// Now write the results out (they OpenSSL key and the serialized key)
|
||||
memcpy(binary_key, serialized_buffer, serialized_key_length);
|
||||
*key_out = key;
|
||||
|
||||
// Successful return.
|
||||
EVP_PKEY_CTX_free(ctx);
|
||||
BIO_free(bio);
|
||||
return true;
|
||||
} while (false);
|
||||
|
||||
// Unsuccessful return.
|
||||
*key_out = nullptr;
|
||||
if (key != nullptr) {
|
||||
EVP_PKEY_free(key);
|
||||
}
|
||||
if (ctx != nullptr) {
|
||||
EVP_PKEY_CTX_free(ctx);
|
||||
}
|
||||
if (bio != nullptr) {
|
||||
BIO_free(bio);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Crypto::DeriveSecretSymmetricKey(EVP_PKEY* local_key,
|
||||
EVP_PKEY* peer_public_key,
|
||||
uint8_t* secret_key) {
|
||||
assert(local_key != nullptr);
|
||||
assert(peer_public_key != nullptr);
|
||||
assert(secret_key != nullptr);
|
||||
|
||||
EVP_PKEY_CTX* ctx = nullptr;
|
||||
|
||||
do {
|
||||
ctx = EVP_PKEY_CTX_new(local_key, /*e=*/nullptr);
|
||||
if (ctx == nullptr) {
|
||||
// warn("Couldn't create an EVP_PKEY_CTX for secret derivation.");
|
||||
ERR_print_errors_fp(stderr);
|
||||
break;
|
||||
}
|
||||
|
||||
if (EVP_PKEY_derive_init(ctx) != 1) {
|
||||
// warn("Couldn't initiate a secret derivation.");
|
||||
ERR_print_errors_fp(stderr);
|
||||
break;
|
||||
}
|
||||
|
||||
if (EVP_PKEY_derive_set_peer(ctx, peer_public_key) != 1) {
|
||||
// warn("Couldn't set the public key of my peer for a secret
|
||||
// derivation.");
|
||||
ERR_print_errors_fp(stderr);
|
||||
break;
|
||||
}
|
||||
|
||||
size_t derived_secret_length = 0;
|
||||
if (EVP_PKEY_derive(ctx, nullptr, &derived_secret_length) != 1) {
|
||||
// warn("Couldn't find the length of the derived secret.");
|
||||
ERR_print_errors_fp(stderr);
|
||||
break;
|
||||
}
|
||||
assert(derived_secret_length <= kSharedSecretLength);
|
||||
uint8_t derived_secret[kSharedSecretLength];
|
||||
|
||||
if (EVP_PKEY_derive(ctx, derived_secret, &derived_secret_length) != 1) {
|
||||
// warn("Couldn't derive a shared secret.");
|
||||
ERR_print_errors_fp(stderr);
|
||||
break;
|
||||
}
|
||||
|
||||
// Now turn it into a key, using a HKDF.
|
||||
// 1. Extract
|
||||
uint8_t expansion[kSharedSecretExpansionLength];
|
||||
// Zero it out, don't use a fancy salt.
|
||||
memset(expansion, 0, kSharedSecretExpansionLength);
|
||||
// First HMAC the shared secret with the expansion as the key (initially
|
||||
// zero).
|
||||
uint32_t hmac_length;
|
||||
uint8_t* hmac = HMAC(EVP_sha256(),
|
||||
/* key = */ expansion, kSharedSecretExpansionLength,
|
||||
/* d = */ derived_secret, derived_secret_length,
|
||||
/* md = */ expansion, &hmac_length);
|
||||
if (hmac == nullptr) {
|
||||
// warn("Couldn't HMAC the derived secret.");
|
||||
ERR_print_errors_fp(stderr);
|
||||
break;
|
||||
}
|
||||
assert(hmac_length == kSharedSecretExpansionLength);
|
||||
// Now HMAC the previous HMAC result with itself as a key, and some
|
||||
// well-defined additional data (namely, 1).
|
||||
uint8_t one = 1;
|
||||
hmac = HMAC(EVP_sha256(),
|
||||
/* key = */ expansion, kSharedSecretExpansionLength,
|
||||
/* d = */ &one, sizeof(one), // arbitrary choice
|
||||
/* md = */ expansion, /* md_len= */ nullptr); // No need to
|
||||
// obtain the
|
||||
// length of the
|
||||
// md yet again.
|
||||
if (hmac == nullptr) {
|
||||
// warn("Couldn't HMAC to expand the symmetric key.");
|
||||
ERR_print_errors_fp(stderr);
|
||||
break;
|
||||
}
|
||||
// Now we have good key material in |expansion|. Strip it down to the
|
||||
// keysize of AES128.
|
||||
assert(kSharedSecretExpansionLength > kSymmetricKeyLength);
|
||||
memcpy(secret_key, expansion, kSymmetricKeyLength);
|
||||
|
||||
EVP_PKEY_CTX_free(ctx);
|
||||
return true;
|
||||
} while (false);
|
||||
|
||||
if (ctx != nullptr) {
|
||||
EVP_PKEY_CTX_free(ctx);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Crypto::Encrypt(const uint8_t* symmetric_key, Encryption* encryption) {
|
||||
assert(encryption != nullptr);
|
||||
|
||||
EVP_CIPHER_CTX* ctx = nullptr;
|
||||
do {
|
||||
ctx = EVP_CIPHER_CTX_new();
|
||||
if (ctx == nullptr) {
|
||||
// warn("Couldn't create a new EVP_CIPHER_CTX.");
|
||||
ERR_print_errors_fp(stderr);
|
||||
break;
|
||||
}
|
||||
|
||||
EVP_CIPHER_CTX_init(ctx);
|
||||
|
||||
// Set up a random nonce
|
||||
if (RAND_bytes(encryption->ToNonce(), kNonceLength) != 1) {
|
||||
// warn("Couldn't generate random nonce.");
|
||||
ERR_print_errors_fp(stderr);
|
||||
break;
|
||||
}
|
||||
|
||||
if (EVP_EncryptInit_ex(ctx, EVP_aes_128_gcm(),
|
||||
/* impl= */ nullptr, symmetric_key,
|
||||
/* iv= */ encryption->ToNonce()) != 1) {
|
||||
// warn("Couldn't initialize for AES128-GCM encryption.");
|
||||
ERR_print_errors_fp(stderr);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!encryption->StreamDataForEncryption(ctx)) {
|
||||
// warn("Couldn't stream data for %s AES128-GCM encryption.",
|
||||
// encryption->TypeString());
|
||||
break;
|
||||
}
|
||||
|
||||
// Now finalize to obtain the tag. We should have no pending ciphertext data
|
||||
// at this point.
|
||||
int32_t out_length;
|
||||
if (EVP_EncryptFinal_ex(ctx, /* out= */ nullptr, &out_length) != 1) {
|
||||
// warn("Couldn't finalize the prochlomation encryption.");
|
||||
ERR_print_errors_fp(stderr);
|
||||
break;
|
||||
}
|
||||
assert(out_length == 0);
|
||||
|
||||
// We have filled in the ciphertext. Now we also need to fill in the tag.
|
||||
if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, kTagLength,
|
||||
encryption->ToTag()) != 1) {
|
||||
// warn("Couldn't obtain the AEAD tag from the prochlomation
|
||||
// encryption.");
|
||||
ERR_print_errors_fp(stderr);
|
||||
break;
|
||||
}
|
||||
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
return true;
|
||||
} while (false);
|
||||
|
||||
if (ctx != nullptr) {
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace prochlo
|
||||
@@ -1,114 +0,0 @@
|
||||
// Copyright 2017 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef BRAVE_COMPONENTS_BRAVE_PROCHLO_PROCHLO_CRYPTO_H_
|
||||
#define BRAVE_COMPONENTS_BRAVE_PROCHLO_PROCHLO_CRYPTO_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "third_party/boringssl/src/include/openssl/evp.h"
|
||||
|
||||
#include "brave/components/brave_prochlo/prochlo_data.h"
|
||||
|
||||
namespace prochlo {
|
||||
|
||||
class Crypto {
|
||||
public:
|
||||
// Load the public key for the Analyzer
|
||||
bool load_analyzer_key(const std::string& keyfile);
|
||||
|
||||
// Load the public key for the Analyzer
|
||||
bool load_shuffler_key(const std::string& keyfile);
|
||||
|
||||
Crypto();
|
||||
~Crypto();
|
||||
|
||||
bool EncryptForAnalyzer(const Prochlomation& prochlomation,
|
||||
AnalyzerItem* analyzer_item);
|
||||
|
||||
bool EncryptForShuffler(const PlainShufflerItem& plain_shuffler_item,
|
||||
ShufflerItem* shuffler_item);
|
||||
|
||||
private:
|
||||
friend class BraveProchloCrypto;
|
||||
|
||||
// A convenient interface for encrypting between pairs of Prochlo messages
|
||||
// without producing a separate serialized copy of the input.
|
||||
class Encryption {
|
||||
public:
|
||||
explicit Encryption(EVP_PKEY* peer_key) : peer_key(peer_key) {}
|
||||
|
||||
EVP_PKEY* ToPeerKey() { return peer_key; }
|
||||
|
||||
virtual bool StreamDataForEncryption(EVP_CIPHER_CTX* ctx) = 0;
|
||||
virtual uint8_t* ToPublicKey() = 0;
|
||||
virtual uint8_t* ToNonce() = 0;
|
||||
virtual uint8_t* ToTag() = 0;
|
||||
virtual const char* TypeString() = 0;
|
||||
|
||||
EVP_PKEY* peer_key;
|
||||
};
|
||||
|
||||
class ProchlomationToAnalyzerItemEncryption : public Crypto::Encryption {
|
||||
public:
|
||||
ProchlomationToAnalyzerItemEncryption(EVP_PKEY* peer_key,
|
||||
const Prochlomation& prochlomation,
|
||||
AnalyzerItem* analyzer_item);
|
||||
const Prochlomation& prochlomation;
|
||||
AnalyzerItem* analyzer_item;
|
||||
|
||||
uint8_t* ToPublicKey() override;
|
||||
const char* TypeString() override;
|
||||
uint8_t* ToNonce() override;
|
||||
uint8_t* ToTag() override;
|
||||
bool StreamDataForEncryption(EVP_CIPHER_CTX* ctx) override;
|
||||
};
|
||||
|
||||
class PlainShufflerItemToShufflerItemEncryption : public Crypto::Encryption {
|
||||
public:
|
||||
PlainShufflerItemToShufflerItemEncryption(
|
||||
EVP_PKEY* peer_key,
|
||||
const PlainShufflerItem& plain_shuffler_item,
|
||||
ShufflerItem* shuffler_item);
|
||||
const PlainShufflerItem& plain_shuffler_item;
|
||||
ShufflerItem* shuffler_item;
|
||||
|
||||
uint8_t* ToPublicKey() override;
|
||||
const char* TypeString() override;
|
||||
uint8_t* ToNonce() override;
|
||||
uint8_t* ToTag() override;
|
||||
bool StreamDataForEncryption(EVP_CIPHER_CTX* ctx) override;
|
||||
};
|
||||
|
||||
bool MakeEncryptedMessage(Encryption* encryption);
|
||||
bool GenerateKeyPair(EVP_PKEY* peer_public_key,
|
||||
EVP_PKEY** key_out,
|
||||
uint8_t* binary_key);
|
||||
|
||||
bool DeriveSecretSymmetricKey(EVP_PKEY* local_key,
|
||||
EVP_PKEY* peer_public_key,
|
||||
uint8_t* secret_key);
|
||||
bool Encrypt(const uint8_t* symmetric_key, Encryption* encryption);
|
||||
|
||||
// Load a public key returning the structure
|
||||
EVP_PKEY* load_public_key(const std::string& keyfile);
|
||||
|
||||
EVP_PKEY* public_shuffler_key_;
|
||||
EVP_PKEY* public_analyzer_key_;
|
||||
};
|
||||
|
||||
} // namespace prochlo
|
||||
|
||||
#endif // BRAVE_COMPONENTS_BRAVE_PROCHLO_PROCHLO_CRYPTO_H_
|
||||
@@ -1,167 +0,0 @@
|
||||
// Copyright 2017 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef BRAVE_COMPONENTS_BRAVE_PROCHLO_PROCHLO_DATA_H_
|
||||
#define BRAVE_COMPONENTS_BRAVE_PROCHLO_PROCHLO_DATA_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
|
||||
namespace prochlo {
|
||||
|
||||
// Nomenclature for message structures:
|
||||
//
|
||||
// The client's encoder produces a Proclomation, i.e., an EncoderItem, which it
|
||||
// wishes to deliver to the Analyzer, via the shuffler.
|
||||
//
|
||||
// A ShufflerItem travels from the Client to the Shuffler.
|
||||
//
|
||||
// An AnalyzerItem travels from the Shuffler to the Analyzer.
|
||||
//
|
||||
// The Shuffler stores the intermediate state of its shuffle on local
|
||||
// (untrusted) storage, in the shape of an IntermediateShufflerItem.
|
||||
//
|
||||
// A Proclomation contains just the type of data (|metric|) and the value. The
|
||||
// Client encrypts using AES128-GCM, with a key derived from its ephemeral key
|
||||
// pair and the Analyzer's public key. That's the inner layer of the nested
|
||||
// encryption, and constitutes the AnalyzerItem.
|
||||
//
|
||||
// Next, the Client constructs the outer layer of the nested encryption, by
|
||||
// adding the value of the crowd id to the AnalyzerItem, and encrypting it,
|
||||
// again using AES128-GCM, with a key derived from (another) ephemeral key pair,
|
||||
// and the Shuffler's public key. That's the outer layer of the nested
|
||||
// encryption, and constitutes the ShufflerItem. This is what the Client
|
||||
// transmits to the Shuffler.
|
||||
//
|
||||
// The StashShuffler shuffles ShufflerItems in at least two rounds, storing
|
||||
// intermediate results on local (but untrusted) storage. To ensure the shuffle
|
||||
// is oblivious, the Shuffler decrypts ShufflerItems (as collected from
|
||||
// Clients), and re-encrypts them unchanged using an ephemeral symmetric key of
|
||||
// its choosing (again using AES128-GCM). The resulting structure is an
|
||||
// IntermediateShufflerItem. In addition to the AnalyzerItem and |crowd_id|, and
|
||||
// IntermediateShufflerItem also contains some StashShuffler metadata (e.g.,
|
||||
// whether an IntermediateShufflerItem is a dummy).
|
||||
//
|
||||
// In a production setting, all of these message structures are represented by
|
||||
// Protocol Buffers, which are not shown here.
|
||||
//
|
||||
// Note that we use struct sizes as the sizes of messages (i.e., sizeof(type)),
|
||||
// rather than the number of bytes they'd take when marshalled. Due to
|
||||
// alignment, the former may be larger than the latter.
|
||||
|
||||
// Default lengths
|
||||
#define DATA_LENGTH 64
|
||||
#define CROWD_ID_LENGTH 8
|
||||
|
||||
// Problem-specific lengths, in bytes.
|
||||
constexpr size_t kProchlomationDataLength = DATA_LENGTH;
|
||||
constexpr size_t kCrowdIdLength = CROWD_ID_LENGTH;
|
||||
|
||||
// Crypto-specific lengths.
|
||||
constexpr size_t kPublicKeyLength = 91; // This is the maximum length we devote
|
||||
// for storing a DER-encoded NIST
|
||||
// P-256 public key.
|
||||
constexpr size_t kSharedSecretLength = 256 / 8; // The length of the derived
|
||||
// shared secret from Diffie
|
||||
// Hellman key exchange on NIST
|
||||
// P-256.
|
||||
constexpr size_t kSymmetricKeyLength = 128 / 8; // The length of an AES128 key.
|
||||
constexpr size_t kSharedSecretExpansionLength = 256 / 8; // The length of the
|
||||
// pseudo-random space
|
||||
// used to derive a
|
||||
// shared symmetric
|
||||
// key from a shared
|
||||
// DH secret. It's
|
||||
// determined by the
|
||||
// length of SHA256.
|
||||
|
||||
constexpr size_t kNonceLength = 12; // The recommended nonce (i.e., IV) length
|
||||
// for AES128-GCM is 12 bytes.
|
||||
|
||||
constexpr size_t kTagLength = 16; // The maximum tag length for AES128-GCM is
|
||||
// 16 bytes.
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Prochlomation
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// A prochlomation is the plain encoded data that a Client's Encoder generates
|
||||
// and intends to deliver to an Analyzer.
|
||||
struct Prochlomation {
|
||||
uint64_t metric;
|
||||
uint8_t data[kProchlomationDataLength]; // e.g., 64
|
||||
};
|
||||
constexpr size_t kProchlomationLength = sizeof(Prochlomation); // e.g., 72
|
||||
constexpr size_t kProchlomationCiphertextLength =
|
||||
kProchlomationLength; // The ciphertext is the same length, but it is
|
||||
// augmented by the MAC stored in |tag| below.
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// AnalyzerItem
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
struct EncryptedProchlomation {
|
||||
// The result of encrypting a Prochlomation using AES128-GCM is |ciphertext|,
|
||||
// with MAC |tag|, starting with the IV in |nonce|.
|
||||
uint8_t ciphertext[kProchlomationCiphertextLength];
|
||||
uint8_t tag[kTagLength];
|
||||
uint8_t nonce[kNonceLength];
|
||||
|
||||
// The key used to produce |ciphertext| is derived from the analyzer's key
|
||||
// pair and the client's ephermeral key pair. The public key of the client's
|
||||
// key pair is |client_public_key|.
|
||||
uint8_t client_public_key[kPublicKeyLength];
|
||||
};
|
||||
constexpr size_t kEncryptedProchlomationLength = sizeof(EncryptedProchlomation);
|
||||
|
||||
// Aliases for EncryptedProchlomations
|
||||
typedef EncryptedProchlomation AnalyzerItem;
|
||||
constexpr size_t kAnalyzerItemLength = kEncryptedProchlomationLength;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// ShufflerItem
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// This is the item that the Shuffler handles, and it contains the AnalyzerItem
|
||||
// and the crowd ID.
|
||||
struct PlainShufflerItem {
|
||||
AnalyzerItem analyzer_item;
|
||||
|
||||
// The crowd ID for the Prochlomation included in |analyzer_item|.
|
||||
uint8_t crowd_id[kCrowdIdLength];
|
||||
};
|
||||
constexpr size_t kPlainShufflerItemLength = sizeof(PlainShufflerItem);
|
||||
|
||||
struct EncryptedPlainShufflerItem {
|
||||
// The result of encrypting an PlainShufflerItem using AES128-GCM is
|
||||
// |ciphertext|, with MAC |tag|, starting with the IV in |nonce|.
|
||||
uint8_t ciphertext[kPlainShufflerItemLength];
|
||||
uint8_t tag[kTagLength];
|
||||
uint8_t nonce[kNonceLength];
|
||||
|
||||
// The key used to produce |ciphertext| is derived from the shuffler's key
|
||||
// pair and the client's ephermeral key pair. The public key of the client's
|
||||
// key pair is |client_public_key|. Note that the client may (in fact, might
|
||||
// as well) use two different ephemeral key pairs, one for the shuffler and
|
||||
// one for the analyzer. So this may not be the same as the
|
||||
// |client_public_key| in EncryptedProchlomation.
|
||||
uint8_t client_public_key[kPublicKeyLength];
|
||||
};
|
||||
constexpr size_t kEncryptedPlainShufflerItemLength =
|
||||
sizeof(EncryptedPlainShufflerItem);
|
||||
|
||||
// The ShufflerItem is just an EncryptedPlainShufflerItem
|
||||
typedef EncryptedPlainShufflerItem ShufflerItem;
|
||||
constexpr size_t kShufflerItemLength = kEncryptedPlainShufflerItemLength;
|
||||
|
||||
} // namespace prochlo
|
||||
|
||||
#endif // BRAVE_COMPONENTS_BRAVE_PROCHLO_PROCHLO_DATA_H_
|
||||
@@ -9,20 +9,7 @@ option optimize_for = LITE_RUNTIME;
|
||||
|
||||
package brave_pyxis;
|
||||
|
||||
message PyxisValue {
|
||||
bytes ciphertext = 1;
|
||||
bytes tag = 2;
|
||||
bytes nonce = 3;
|
||||
fixed64 metric_id = 4;
|
||||
|
||||
bytes client_public_key = 5;
|
||||
}
|
||||
|
||||
message RawP3AValue {
|
||||
fixed64 metric_id = 1;
|
||||
bytes p3a_info = 2;
|
||||
}
|
||||
|
||||
message PyxisMessage {
|
||||
repeated PyxisValue pyxis_values = 1;
|
||||
}
|
||||
|
||||
@@ -22,13 +22,14 @@ static_library("p3a") {
|
||||
"brave_p3a_utils.h",
|
||||
"histograms_braveizer.cc",
|
||||
"histograms_braveizer.h",
|
||||
"p3a_message.cc",
|
||||
"p3a_message.h",
|
||||
"pref_names.cc",
|
||||
"pref_names.h",
|
||||
]
|
||||
|
||||
deps = [
|
||||
"//base",
|
||||
"//brave/components/brave_prochlo",
|
||||
"//brave/components/brave_prochlo:prochlo_proto",
|
||||
"//brave/components/brave_referrals/common",
|
||||
"//brave/components/brave_stats/browser",
|
||||
@@ -41,6 +42,7 @@ static_library("p3a") {
|
||||
"//content/public/common",
|
||||
"//services/network/public/cpp",
|
||||
"//services/network/public/mojom",
|
||||
"//third_party/protobuf:protobuf_lite",
|
||||
"//url",
|
||||
]
|
||||
}
|
||||
|
||||
@@ -248,7 +248,7 @@ void BraveP3AService::Init(
|
||||
<< ", upload_server_url_ = " << upload_server_url_.spec()
|
||||
<< ", rotation_interval_ = " << rotation_interval_;
|
||||
|
||||
InitPyxisMeta();
|
||||
InitMessageMeta();
|
||||
|
||||
// Init log store.
|
||||
log_store_.reset(new BraveP3ALogStore(this, local_state_));
|
||||
@@ -300,15 +300,9 @@ std::string BraveP3AService::Serialize(base::StringPiece histogram_name,
|
||||
// point when the actual histogram is not ready yet.
|
||||
const uint64_t histogram_name_hash = base::HashMetricName(histogram_name);
|
||||
|
||||
// TODO(iefremov): Restore when PROCHLO/PYXIS is ready.
|
||||
// brave_pyxis::PyxisMessage message;
|
||||
// prochlo::GenerateProchloMessage(histogram_name_hash, value, pyxis_meta_,
|
||||
// &message);
|
||||
|
||||
UpdatePyxisMeta();
|
||||
UpdateMessageMeta();
|
||||
brave_pyxis::RawP3AValue message;
|
||||
prochlo::GenerateP3AMessage(histogram_name_hash, value, pyxis_meta_,
|
||||
&message);
|
||||
GenerateP3AMessage(histogram_name_hash, value, message_meta_, &message);
|
||||
return message.SerializeAsString();
|
||||
}
|
||||
|
||||
@@ -354,35 +348,37 @@ void BraveP3AService::MaybeOverrideSettingsFromCommandLine() {
|
||||
}
|
||||
}
|
||||
|
||||
void BraveP3AService::InitPyxisMeta() {
|
||||
pyxis_meta_.platform = brave_stats::GetPlatformIdentifier();
|
||||
pyxis_meta_.channel = channel_;
|
||||
pyxis_meta_.version =
|
||||
void BraveP3AService::InitMessageMeta() {
|
||||
message_meta_.platform = brave_stats::GetPlatformIdentifier();
|
||||
message_meta_.channel = channel_;
|
||||
message_meta_.version =
|
||||
version_info::GetBraveVersionWithoutChromiumMajorVersion();
|
||||
|
||||
if (!week_of_install_.empty()) {
|
||||
pyxis_meta_.date_of_install = brave_stats::GetYMDAsDate(week_of_install_);
|
||||
message_meta_.date_of_install = brave_stats::GetYMDAsDate(week_of_install_);
|
||||
} else {
|
||||
pyxis_meta_.date_of_install = base::Time::Now();
|
||||
message_meta_.date_of_install = base::Time::Now();
|
||||
}
|
||||
pyxis_meta_.woi = brave_stats::GetIsoWeekNumber(pyxis_meta_.date_of_install);
|
||||
message_meta_.woi =
|
||||
brave_stats::GetIsoWeekNumber(message_meta_.date_of_install);
|
||||
|
||||
pyxis_meta_.country_code =
|
||||
message_meta_.country_code =
|
||||
base::ToUpperASCII(base::CountryCodeForCurrentTimezone());
|
||||
pyxis_meta_.refcode = local_state_->GetString(kReferralPromoCode);
|
||||
MaybeStripRefcodeAndCountry(&pyxis_meta_);
|
||||
message_meta_.refcode = local_state_->GetString(kReferralPromoCode);
|
||||
MaybeStripRefcodeAndCountry(&message_meta_);
|
||||
|
||||
UpdatePyxisMeta();
|
||||
UpdateMessageMeta();
|
||||
|
||||
VLOG(2) << "Pyxis meta: " << pyxis_meta_.platform << " "
|
||||
<< pyxis_meta_.channel << " " << pyxis_meta_.version << " "
|
||||
<< pyxis_meta_.woi << " " << pyxis_meta_.wos << " "
|
||||
<< pyxis_meta_.country_code << " " << pyxis_meta_.refcode;
|
||||
VLOG(2) << "Message meta: " << message_meta_.platform << " "
|
||||
<< message_meta_.channel << " " << message_meta_.version << " "
|
||||
<< message_meta_.woi << " " << message_meta_.wos << " "
|
||||
<< message_meta_.country_code << " " << message_meta_.refcode;
|
||||
}
|
||||
|
||||
void BraveP3AService::UpdatePyxisMeta() {
|
||||
pyxis_meta_.date_of_survey = base::Time::Now();
|
||||
pyxis_meta_.wos = brave_stats::GetIsoWeekNumber(pyxis_meta_.date_of_survey);
|
||||
void BraveP3AService::UpdateMessageMeta() {
|
||||
message_meta_.date_of_survey = base::Time::Now();
|
||||
message_meta_.wos =
|
||||
brave_stats::GetIsoWeekNumber(message_meta_.date_of_survey);
|
||||
}
|
||||
|
||||
void BraveP3AService::StartScheduledUpload() {
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
#include "base/metrics/histogram_base.h"
|
||||
#include "base/metrics/statistics_recorder.h"
|
||||
#include "base/timer/timer.h"
|
||||
#include "brave/components/brave_prochlo/brave_prochlo_message.h"
|
||||
#include "brave/components/p3a/brave_p3a_log_store.h"
|
||||
#include "brave/components/p3a/p3a_message.h"
|
||||
#include "url/gurl.h"
|
||||
|
||||
class PrefRegistrySimple;
|
||||
@@ -64,10 +64,10 @@ class BraveP3AService : public base::RefCountedThreadSafe<BraveP3AService>,
|
||||
|
||||
void MaybeOverrideSettingsFromCommandLine();
|
||||
|
||||
void InitPyxisMeta();
|
||||
void InitMessageMeta();
|
||||
|
||||
// Updates things that change over time: week of survey, etc.
|
||||
void UpdatePyxisMeta();
|
||||
void UpdateMessageMeta();
|
||||
|
||||
void StartScheduledUpload();
|
||||
|
||||
@@ -105,7 +105,7 @@ class BraveP3AService : public base::RefCountedThreadSafe<BraveP3AService>,
|
||||
base::TimeDelta rotation_interval_;
|
||||
GURL upload_server_url_;
|
||||
|
||||
prochlo::MessageMetainfo pyxis_meta_;
|
||||
MessageMetainfo message_meta_;
|
||||
|
||||
// Components:
|
||||
std::unique_ptr<BraveP3ALogStore> log_store_;
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
/* Copyright 2021 The Brave Authors. All rights reserved.
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "brave/components/p3a/p3a_message.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "base/containers/flat_set.h"
|
||||
#include "base/cxx17_backports.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/strings/string_number_conversions.h"
|
||||
#include "base/trace_event/trace_event.h"
|
||||
#include "brave/components/brave_prochlo/prochlo_message.pb.h"
|
||||
#include "crypto/sha2.h"
|
||||
|
||||
namespace brave {
|
||||
|
||||
MessageMetainfo::MessageMetainfo() = default;
|
||||
MessageMetainfo::~MessageMetainfo() = default;
|
||||
|
||||
void GenerateP3AMessage(uint64_t metric_hash,
|
||||
uint64_t metric_value,
|
||||
const MessageMetainfo& meta,
|
||||
brave_pyxis::RawP3AValue* p3a_message) {
|
||||
// TODO(iefremov): - create patch for adding `brave_p3a`
|
||||
// to src/base/trace_event/builtin_categories.h
|
||||
// TRACE_EVENT0("brave_p3a", "GenerateP3AMessage");
|
||||
constexpr size_t kDataLength = 64;
|
||||
uint8_t data[kDataLength] = {0};
|
||||
|
||||
// First byte contains the 4 booleans.
|
||||
const char daily = 1;
|
||||
const char weekly = 0;
|
||||
const char monthly = 2;
|
||||
const char first = 0;
|
||||
data[0] = daily | weekly | monthly | first;
|
||||
uint8_t* ptr = data;
|
||||
ptr++;
|
||||
|
||||
// Find out years of install and survey.
|
||||
base::Time::Exploded exploded;
|
||||
meta.date_of_survey.LocalExplode(&exploded);
|
||||
DCHECK_GE(exploded.year, 999);
|
||||
const std::string yos = base::NumberToString(exploded.year).substr(2, 4);
|
||||
meta.date_of_install.LocalExplode(&exploded);
|
||||
DCHECK_GE(exploded.year, 999);
|
||||
const std::string yoi = base::NumberToString(exploded.year).substr(2, 4);
|
||||
|
||||
const std::string metastring =
|
||||
"," + meta.country_code + "," + meta.platform + "," + meta.version + "," +
|
||||
meta.channel + "," + yoi + base::NumberToString(meta.woi) + "," + yos +
|
||||
base::NumberToString(meta.wos) + "," + meta.refcode + ",";
|
||||
|
||||
const std::string metric_value_str = base::NumberToString(metric_value);
|
||||
|
||||
// TODO(iefremov): replace with 'if'?
|
||||
CHECK_LE(metastring.size() + metric_value_str.size(), kDataLength - 1);
|
||||
|
||||
memcpy(ptr, metastring.data(), metastring.size());
|
||||
ptr += metastring.size();
|
||||
memcpy(ptr, metric_value_str.data(), metric_value_str.size());
|
||||
|
||||
// Init the message.
|
||||
p3a_message->set_metric_id(metric_hash);
|
||||
p3a_message->set_p3a_info(data, kDataLength);
|
||||
}
|
||||
|
||||
void MaybeStripRefcodeAndCountry(MessageMetainfo* meta) {
|
||||
const std::string& refcode = meta->refcode;
|
||||
const std::string& country = meta->country_code;
|
||||
constexpr char kRefcodeNone[] = "none";
|
||||
constexpr char kCountryOther[] = "other";
|
||||
constexpr char kRefcodeOther[] = "other";
|
||||
|
||||
static base::flat_set<std::string> const kLinuxCountries(
|
||||
{"US", "FR", "DE", "GB", "IN", "BR", "PL", "NL", "ES", "CA", "IT", "AU",
|
||||
"MX", "CH", "RU", "ZA", "SE", "BE", "JP"});
|
||||
|
||||
static base::flat_set<std::string> const kNotableRefcodes(
|
||||
{"BRV001", "GDB255", "APP709", "GBW423", "BRT001", "VNI569", "ICO964",
|
||||
"ILY758"});
|
||||
|
||||
static base::flat_set<std::string> const kNotableCountries(
|
||||
{"FR", "PH", "GB", "IN", "DE", "BR", "CA", "IT", "ES", "NL", "MX", "AU",
|
||||
"RU", "JP", "PL", "ID", "KR", "AR"});
|
||||
|
||||
DCHECK(meta);
|
||||
if (meta->platform == "linux-bc") {
|
||||
// Because Linux has no refcodes, ignore, and if we have more than
|
||||
// 3/0.05 = 60 users in a country for a week of install, we can send
|
||||
// country.
|
||||
meta->refcode = kRefcodeNone;
|
||||
if (kLinuxCountries.count(country) == 0) {
|
||||
meta->country_code = kCountryOther;
|
||||
}
|
||||
} else {
|
||||
// Now the minimum platform is MacOS at ~3%, so cut off for a group under
|
||||
// here becomes 3/(0.05*0.03) = 2000.
|
||||
if (country == "US" || country.empty()) {
|
||||
const bool us_and_ref =
|
||||
country == "US" && (refcode == kRefcodeNone || refcode == "GDB255" ||
|
||||
refcode == "BRV001");
|
||||
const bool unknown_and_ref =
|
||||
country.empty() &&
|
||||
(kNotableRefcodes.count(refcode) > 0 || refcode == kRefcodeNone);
|
||||
|
||||
if (!(us_and_ref || unknown_and_ref)) {
|
||||
meta->refcode = kRefcodeOther;
|
||||
}
|
||||
} else if (kNotableCountries.count(country) > 0) {
|
||||
meta->refcode = kRefcodeOther;
|
||||
} else {
|
||||
meta->country_code = kCountryOther;
|
||||
meta->refcode = kRefcodeOther;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace brave
|
||||
@@ -1,22 +1,20 @@
|
||||
/* Copyright 2019 The Brave Authors. All rights reserved.
|
||||
/* Copyright 2021 The Brave Authors. All rights reserved.
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef BRAVE_COMPONENTS_BRAVE_PROCHLO_BRAVE_PROCHLO_MESSAGE_H_
|
||||
#define BRAVE_COMPONENTS_BRAVE_PROCHLO_BRAVE_PROCHLO_MESSAGE_H_
|
||||
#ifndef BRAVE_COMPONENTS_P3A_P3A_MESSAGE_H_
|
||||
#define BRAVE_COMPONENTS_P3A_P3A_MESSAGE_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include "base/time/time.h"
|
||||
|
||||
namespace brave_pyxis {
|
||||
class PyxisMessage;
|
||||
class RawP3AValue;
|
||||
}
|
||||
|
||||
// TODO(iefremov): prochlo -> pyxis everywhere.
|
||||
namespace prochlo {
|
||||
namespace brave {
|
||||
|
||||
struct MessageMetainfo {
|
||||
MessageMetainfo();
|
||||
@@ -25,19 +23,14 @@ struct MessageMetainfo {
|
||||
std::string platform;
|
||||
std::string version;
|
||||
std::string channel;
|
||||
base::Time date_of_install;
|
||||
base::Time date_of_survey;
|
||||
base::Time date_of_install;
|
||||
base::Time date_of_survey;
|
||||
int woi; // Week of install. Remove this occasionally and extract from above.
|
||||
int wos; // Week of survey.
|
||||
std::string country_code;
|
||||
std::string refcode;
|
||||
};
|
||||
|
||||
void GenerateProchloMessage(uint64_t metric_hash,
|
||||
uint64_t metric_value,
|
||||
const MessageMetainfo& meta,
|
||||
brave_pyxis::PyxisMessage* prochlo_message);
|
||||
|
||||
void GenerateP3AMessage(uint64_t metric_hash,
|
||||
uint64_t metric_value,
|
||||
const MessageMetainfo& meta,
|
||||
@@ -45,8 +38,8 @@ void GenerateP3AMessage(uint64_t metric_hash,
|
||||
|
||||
// Ensures that country/refcode represent the big enough cohort that will not
|
||||
// let anybody identify the sender.
|
||||
void MaybeStripRefcodeAndCountry(prochlo::MessageMetainfo* meta);
|
||||
void MaybeStripRefcodeAndCountry(MessageMetainfo* meta);
|
||||
|
||||
} // namespace prochlo
|
||||
} // namespace brave
|
||||
|
||||
#endif // BRAVE_COMPONENTS_BRAVE_PROCHLO_BRAVE_PROCHLO_MESSAGE_H_
|
||||
#endif // BRAVE_COMPONENTS_P3A_P3A_MESSAGE_H_
|
||||
@@ -127,7 +127,6 @@ def AddBraveCredits(prune_paths, special_cases, prune_dirs, additional_paths):
|
||||
# contained under a "third_party" or "vendor" directory.
|
||||
additional_list = list(additional_paths)
|
||||
additional_list += [
|
||||
os.path.join('brave', 'components', 'brave_prochlo'),
|
||||
os.path.join('brave', 'components', 'brave_new_tab_ui', 'data'),
|
||||
]
|
||||
additional_paths = tuple(additional_list)
|
||||
|
||||
Reference in New Issue
Block a user